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 | clean: |
6 | $(RM) $(example_binaries) |
7 | |
8 | -# Reformat the source files to match our layout standards. |
9 | -# This includes gofmt's "simplify" option to streamline the source code. |
10 | +# Reformat source files. |
11 | format: |
12 | - ./utilities/format -s |
13 | + gofmt -w -l . |
14 | + |
15 | +# Reformat and simplify source files. |
16 | +simplify: |
17 | + gofmt -w -l -s . |
18 | |
19 | # Build the examples (we have no tests for them). |
20 | examples: $(example_binaries) |
21 | |
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 | package dedent |
27 | |
28 | import ( |
29 | - "regexp" |
30 | - "strings" |
31 | + "regexp" |
32 | + "strings" |
33 | ) |
34 | |
35 | const emptyString = "" |
36 | @@ -14,7 +14,7 @@ |
37 | |
38 | // Split the given text into lines. |
39 | func splitLines(text string) []string { |
40 | - return reLine.FindAllString(text, -1) |
41 | + return reLine.FindAllString(text, -1) |
42 | } |
43 | |
44 | // Match leading whitespace or tabs. \p{Zs} is a Unicode character class: |
45 | @@ -23,46 +23,46 @@ |
46 | |
47 | // Find the longest leading margin common between the given lines. |
48 | func calculateMargin(lines []string) string { |
49 | - var margin string |
50 | - var first bool = true |
51 | - for _, line := range lines { |
52 | - indent := reLeadingWhitespace.FindString(line) |
53 | - switch { |
54 | - case len(indent) == len(line): |
55 | - // The line is either empty or whitespace and will be ignored for |
56 | - // the purposes of calculating the margin. |
57 | - case first: |
58 | - // This is the first line with an indent, so start from here. |
59 | - margin = indent |
60 | - first = false |
61 | - case strings.HasPrefix(indent, margin): |
62 | - // This line's indent is longer or equal to the margin. The |
63 | - // current margin remains unalterered. |
64 | - case strings.HasPrefix(margin, indent): |
65 | - // This line's indent is compatible with the margin but shorter |
66 | - // (strictly it could be equal, however that condition is handled |
67 | - // earlier in this switch). The current indent becomes the margin. |
68 | - margin = indent |
69 | - default: |
70 | - // There is no common margin so stop scanning. |
71 | - return emptyString |
72 | - } |
73 | - } |
74 | - return margin |
75 | + var margin string |
76 | + var first bool = true |
77 | + for _, line := range lines { |
78 | + indent := reLeadingWhitespace.FindString(line) |
79 | + switch { |
80 | + case len(indent) == len(line): |
81 | + // The line is either empty or whitespace and will be ignored for |
82 | + // the purposes of calculating the margin. |
83 | + case first: |
84 | + // This is the first line with an indent, so start from here. |
85 | + margin = indent |
86 | + first = false |
87 | + case strings.HasPrefix(indent, margin): |
88 | + // This line's indent is longer or equal to the margin. The |
89 | + // current margin remains unalterered. |
90 | + case strings.HasPrefix(margin, indent): |
91 | + // This line's indent is compatible with the margin but shorter |
92 | + // (strictly it could be equal, however that condition is handled |
93 | + // earlier in this switch). The current indent becomes the margin. |
94 | + margin = indent |
95 | + default: |
96 | + // There is no common margin so stop scanning. |
97 | + return emptyString |
98 | + } |
99 | + } |
100 | + return margin |
101 | } |
102 | |
103 | // Remove a prefix from each line, if present. |
104 | func trimPrefix(lines []string, prefix string) { |
105 | - trim := len(prefix) |
106 | - for i, line := range lines { |
107 | - if strings.HasPrefix(line, prefix) { |
108 | - lines[i] = line[trim:] |
109 | - } |
110 | - } |
111 | + trim := len(prefix) |
112 | + for i, line := range lines { |
113 | + if strings.HasPrefix(line, prefix) { |
114 | + lines[i] = line[trim:] |
115 | + } |
116 | + } |
117 | } |
118 | |
119 | func Dedent(text string) string { |
120 | - lines := splitLines(text) |
121 | - trimPrefix(lines, calculateMargin(lines)) |
122 | - return strings.Join(lines, "\n") |
123 | + lines := splitLines(text) |
124 | + trimPrefix(lines, calculateMargin(lines)) |
125 | + return strings.Join(lines, "\n") |
126 | } |
127 | |
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 | package dedent |
133 | |
134 | import ( |
135 | - . "launchpad.net/gocheck" |
136 | - "testing" |
137 | + . "launchpad.net/gocheck" |
138 | + "testing" |
139 | ) |
140 | |
141 | type dedentSuite struct{} |
142 | @@ -14,81 +14,81 @@ |
143 | |
144 | // Dedent() does nothing with the empty string. |
145 | func (suite *dedentSuite) TestEmptyString(c *C) { |
146 | - input := "" |
147 | - expected := input |
148 | - observed := Dedent(input) |
149 | - c.Check(observed, Equals, expected) |
150 | + input := "" |
151 | + expected := input |
152 | + observed := Dedent(input) |
153 | + c.Check(observed, Equals, expected) |
154 | } |
155 | |
156 | // Dedent() does nothing to a single line without an indent. |
157 | func (suite *dedentSuite) TestSingleLine(c *C) { |
158 | - input := "This is a single line." |
159 | - expected := input |
160 | - observed := Dedent(input) |
161 | - c.Check(observed, Equals, expected) |
162 | + input := "This is a single line." |
163 | + expected := input |
164 | + observed := Dedent(input) |
165 | + c.Check(observed, Equals, expected) |
166 | } |
167 | |
168 | // Dedent() removes all leading whitespace from single lines. |
169 | func (suite *dedentSuite) TestSingleLineWithIndent(c *C) { |
170 | - input := " This is a single line." |
171 | - expected := "This is a single line." |
172 | - observed := Dedent(input) |
173 | - c.Check(observed, Equals, expected) |
174 | + input := " This is a single line." |
175 | + expected := "This is a single line." |
176 | + observed := Dedent(input) |
177 | + c.Check(observed, Equals, expected) |
178 | } |
179 | |
180 | // Dedent() does nothing when none of the lines are indented. |
181 | func (suite *dedentSuite) TestLines(c *C) { |
182 | - input := "One\nTwo\n" |
183 | - expected := input |
184 | - observed := Dedent(input) |
185 | - c.Check(observed, Equals, expected) |
186 | + input := "One\nTwo\n" |
187 | + expected := input |
188 | + observed := Dedent(input) |
189 | + c.Check(observed, Equals, expected) |
190 | } |
191 | |
192 | // Dedent() does nothing when *any* line is not indented. |
193 | func (suite *dedentSuite) TestLinesWithSomeIndents(c *C) { |
194 | - input := "One\n Two\n" |
195 | - expected := input |
196 | - observed := Dedent(input) |
197 | - c.Check(observed, Equals, expected) |
198 | + input := "One\n Two\n" |
199 | + expected := input |
200 | + observed := Dedent(input) |
201 | + c.Check(observed, Equals, expected) |
202 | } |
203 | |
204 | // Dedent() removes the common leading indent from each line. |
205 | func (suite *dedentSuite) TestLinesWithIndents(c *C) { |
206 | - input := " One\n Two\n" |
207 | - expected := "One\n Two\n" |
208 | - observed := Dedent(input) |
209 | - c.Check(observed, Equals, expected) |
210 | + input := " One\n Two\n" |
211 | + expected := "One\n Two\n" |
212 | + observed := Dedent(input) |
213 | + c.Check(observed, Equals, expected) |
214 | } |
215 | |
216 | // Dedent() ignores all-whitespace lines for the purposes of margin |
217 | // calculation. However, the margin *is* trimmed from these lines, if they |
218 | // begin with it. |
219 | func (suite *dedentSuite) TestLinesWithEmptyLine(c *C) { |
220 | - input := " One\n \n Three\n" |
221 | - expected := "One\n \nThree\n" |
222 | - observed := Dedent(input) |
223 | - c.Check(observed, Equals, expected) |
224 | + input := " One\n \n Three\n" |
225 | + expected := "One\n \nThree\n" |
226 | + observed := Dedent(input) |
227 | + c.Check(observed, Equals, expected) |
228 | } |
229 | |
230 | // Dedent() ignores blank lines for the purposes of margin calculation, |
231 | // including the first line. |
232 | func (suite *dedentSuite) TestLinesWithEmptyFirstLine(c *C) { |
233 | - input := "\n Two\n Three\n" |
234 | - expected := "\nTwo\nThree\n" |
235 | - observed := Dedent(input) |
236 | - c.Check(observed, Equals, expected) |
237 | + input := "\n Two\n Three\n" |
238 | + expected := "\nTwo\nThree\n" |
239 | + observed := Dedent(input) |
240 | + c.Check(observed, Equals, expected) |
241 | } |
242 | |
243 | // Dedent() treats spaces and tabs as completely different; no number of |
244 | // spaces is equivalent to a tab. |
245 | func (suite *dedentSuite) TestLinesWithTabsAndSpaces(c *C) { |
246 | - input := "\tOne\n Two\n" |
247 | - expected := input |
248 | - observed := Dedent(input) |
249 | - c.Check(observed, Equals, expected) |
250 | + input := "\tOne\n Two\n" |
251 | + expected := input |
252 | + observed := Dedent(input) |
253 | + c.Check(observed, Equals, expected) |
254 | } |
255 | |
256 | // Master loader for all tests. |
257 | func Test(t *testing.T) { |
258 | - TestingT(t) |
259 | + TestingT(t) |
260 | } |
261 | |
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 | package gwacl |
267 | |
268 | import ( |
269 | - "fmt" |
270 | - "regexp" |
271 | - "time" |
272 | + "fmt" |
273 | + "regexp" |
274 | + "time" |
275 | ) |
276 | |
277 | var deleteDiskTimeout = 30 * time.Minute |
278 | var deleteDiskInterval = 10 * time.Second |
279 | |
280 | type diskDeletePoller struct { |
281 | - api *ManagementAPI |
282 | - diskName string |
283 | - deleteBlob bool |
284 | + api *ManagementAPI |
285 | + diskName string |
286 | + deleteBlob bool |
287 | } |
288 | |
289 | var _ poller = &diskDeletePoller{} |
290 | |
291 | func (poller diskDeletePoller) poll() (*x509Response, error) { |
292 | - return nil, poller.api._DeleteDisk(poller.diskName, poller.deleteBlob) |
293 | + return nil, poller.api._DeleteDisk(poller.diskName, poller.deleteBlob) |
294 | } |
295 | |
296 | // isInUseError returns whether or not the given string is of the "disk in use" |
297 | @@ -46,20 +46,20 @@ |
298 | // gwacl623yosxtppsa9577xy5, deployment gwaclmachinewes4n64f. (http |
299 | // code 400: Bad Request)" |
300 | func isInUseError(errString string, diskName string) bool { |
301 | - pattern := fmt.Sprintf("BadRequest - A disk with name %s is currently in use by virtual machine.*", regexp.QuoteMeta(diskName)) |
302 | - reg := regexp.MustCompile(pattern) |
303 | - return reg.MatchString(errString) |
304 | + pattern := fmt.Sprintf("BadRequest - A disk with name %s is currently in use by virtual machine.*", regexp.QuoteMeta(diskName)) |
305 | + reg := regexp.MustCompile(pattern) |
306 | + return reg.MatchString(errString) |
307 | } |
308 | |
309 | func (poller diskDeletePoller) isDone(response *x509Response, pollerErr error) (bool, error) { |
310 | - if pollerErr == nil { |
311 | - return true, nil |
312 | - } |
313 | - if isInUseError(pollerErr.Error(), poller.diskName) { |
314 | - // The error is of the "disk in use" type: continue polling. |
315 | - return false, nil |
316 | - } |
317 | - // The error is *not* of the "disk in use" type: stop polling and return |
318 | - // the error. |
319 | - return true, pollerErr |
320 | + if pollerErr == nil { |
321 | + return true, nil |
322 | + } |
323 | + if isInUseError(pollerErr.Error(), poller.diskName) { |
324 | + // The error is of the "disk in use" type: continue polling. |
325 | + return false, nil |
326 | + } |
327 | + // The error is *not* of the "disk in use" type: stop polling and return |
328 | + // the error. |
329 | + return true, pollerErr |
330 | } |
331 | |
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 | package gwacl |
337 | |
338 | import ( |
339 | - "fmt" |
340 | - . "launchpad.net/gocheck" |
341 | - "net/http" |
342 | - "time" |
343 | + "fmt" |
344 | + . "launchpad.net/gocheck" |
345 | + "net/http" |
346 | + "time" |
347 | ) |
348 | |
349 | type deleteDiskSuite struct{} |
350 | @@ -16,88 +16,88 @@ |
351 | |
352 | // Real-world error messages and names. |
353 | const ( |
354 | - 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)" |
355 | - diskName = "gwacldiske5w7lkj" |
356 | - diskDoesNotExistError = "DELETE request failed: ResourceNotFound - The disk with the specified name does not exist. (http code 404: Not Found)" |
357 | + 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 | + diskName = "gwacldiske5w7lkj" |
359 | + diskDoesNotExistError = "DELETE request failed: ResourceNotFound - The disk with the specified name does not exist. (http code 404: Not Found)" |
360 | ) |
361 | |
362 | func (suite *deleteDiskSuite) TestIsInUseError(c *C) { |
363 | - var testValues = []struct { |
364 | - errorString string |
365 | - diskName string |
366 | - expectedResult bool |
367 | - }{ |
368 | - {fmt.Sprintf(diskInUseErrorTemplate, diskName), diskName, true}, |
369 | - {fmt.Sprintf(diskInUseErrorTemplate, diskName), "another-disk", false}, |
370 | - {"unknown error", diskName, false}, |
371 | - {diskDoesNotExistError, diskName, false}, |
372 | - } |
373 | - for _, test := range testValues { |
374 | - c.Check(isInUseError(test.errorString, test.diskName), Equals, test.expectedResult) |
375 | - } |
376 | + var testValues = []struct { |
377 | + errorString string |
378 | + diskName string |
379 | + expectedResult bool |
380 | + }{ |
381 | + {fmt.Sprintf(diskInUseErrorTemplate, diskName), diskName, true}, |
382 | + {fmt.Sprintf(diskInUseErrorTemplate, diskName), "another-disk", false}, |
383 | + {"unknown error", diskName, false}, |
384 | + {diskDoesNotExistError, diskName, false}, |
385 | + } |
386 | + for _, test := range testValues { |
387 | + c.Check(isInUseError(test.errorString, test.diskName), Equals, test.expectedResult) |
388 | + } |
389 | } |
390 | |
391 | func (suite *deleteDiskSuite) TestIsDoneReturnsTrueIfNilError(c *C) { |
392 | - poller := diskDeletePoller{nil, "", false} |
393 | - randomResponse := x509Response{StatusCode: http.StatusAccepted} |
394 | - done, err := poller.isDone(&randomResponse, nil) |
395 | - c.Check(done, Equals, true) |
396 | - c.Check(err, IsNil) |
397 | + poller := diskDeletePoller{nil, "", false} |
398 | + randomResponse := x509Response{StatusCode: http.StatusAccepted} |
399 | + done, err := poller.isDone(&randomResponse, nil) |
400 | + c.Check(done, Equals, true) |
401 | + c.Check(err, IsNil) |
402 | } |
403 | |
404 | func (suite *deleteDiskSuite) TestIsDoneReturnsFalseIfDiskInUseError(c *C) { |
405 | - diskName := "gwacldiske5w7lkj" |
406 | - diskInUseError := fmt.Errorf(diskInUseErrorTemplate, diskName) |
407 | - poller := diskDeletePoller{nil, diskName, false} |
408 | - done, err := poller.isDone(nil, diskInUseError) |
409 | - c.Check(done, Equals, false) |
410 | - c.Check(err, IsNil) |
411 | + diskName := "gwacldiske5w7lkj" |
412 | + diskInUseError := fmt.Errorf(diskInUseErrorTemplate, diskName) |
413 | + poller := diskDeletePoller{nil, diskName, false} |
414 | + done, err := poller.isDone(nil, diskInUseError) |
415 | + c.Check(done, Equals, false) |
416 | + c.Check(err, IsNil) |
417 | } |
418 | |
419 | func (suite *deleteDiskSuite) TestIsDoneReturnsTrueIfAnotherError(c *C) { |
420 | - anotherError := fmt.Errorf("Unknown error") |
421 | - poller := diskDeletePoller{nil, "disk-name", false} |
422 | - done, err := poller.isDone(nil, anotherError) |
423 | - c.Check(done, Equals, true) |
424 | - c.Check(err, Equals, anotherError) |
425 | + anotherError := fmt.Errorf("Unknown error") |
426 | + poller := diskDeletePoller{nil, "disk-name", false} |
427 | + done, err := poller.isDone(nil, anotherError) |
428 | + c.Check(done, Equals, true) |
429 | + c.Check(err, Equals, anotherError) |
430 | } |
431 | |
432 | func (suite *deleteDiskSuite) TestPollCallsDeleteDisk(c *C) { |
433 | - api := makeAPI(c) |
434 | - recordedRequests := setUpDispatcher("operationID") |
435 | - diskName := "gwacldiske5w7lkj" |
436 | - poller := diskDeletePoller{api, diskName, false} |
437 | - |
438 | - response, err := poller.poll() |
439 | - |
440 | - c.Assert(response, IsNil) |
441 | - c.Assert(err, IsNil) |
442 | - expectedURL := api.session.composeURL("services/disks/" + diskName) |
443 | - checkOneRequest(c, recordedRequests, expectedURL, "2012-08-01", nil, "DELETE") |
444 | + api := makeAPI(c) |
445 | + recordedRequests := setUpDispatcher("operationID") |
446 | + diskName := "gwacldiske5w7lkj" |
447 | + poller := diskDeletePoller{api, diskName, false} |
448 | + |
449 | + response, err := poller.poll() |
450 | + |
451 | + c.Assert(response, IsNil) |
452 | + c.Assert(err, IsNil) |
453 | + expectedURL := api.session.composeURL("services/disks/" + diskName) |
454 | + checkOneRequest(c, recordedRequests, expectedURL, "2012-08-01", nil, "DELETE") |
455 | } |
456 | |
457 | func (suite *deleteDiskSuite) TestManagementAPIDeleteDiskPolls(c *C) { |
458 | - firstResponse := DispatcherResponse{ |
459 | - response: &x509Response{}, |
460 | - errorObject: fmt.Errorf(diskInUseErrorTemplate, diskName)} |
461 | - secondResponse := DispatcherResponse{ |
462 | - response: &x509Response{StatusCode: http.StatusOK}, |
463 | - errorObject: nil} |
464 | - responses := []DispatcherResponse{firstResponse, secondResponse} |
465 | - rigPreparedResponseDispatcher(responses) |
466 | - recordedRequests := make([]*X509Request, 0) |
467 | - rigRecordingDispatcher(&recordedRequests) |
468 | - |
469 | - api := makeAPI(c) |
470 | - diskName := "gwacldiske5w7lkj" |
471 | - poller := diskDeletePoller{api, diskName, false} |
472 | - |
473 | - response, err := performPolling(poller, time.Nanosecond, time.Minute) |
474 | - |
475 | - c.Assert(response, IsNil) |
476 | - c.Assert(err, IsNil) |
477 | - expectedURL := api.session.composeURL("services/disks/" + diskName) |
478 | - c.Check(len(recordedRequests), Equals, 2) |
479 | - checkRequest(c, recordedRequests[0], expectedURL, "2012-08-01", nil, "DELETE") |
480 | - checkRequest(c, recordedRequests[1], expectedURL, "2012-08-01", nil, "DELETE") |
481 | + firstResponse := DispatcherResponse{ |
482 | + response: &x509Response{}, |
483 | + errorObject: fmt.Errorf(diskInUseErrorTemplate, diskName)} |
484 | + secondResponse := DispatcherResponse{ |
485 | + response: &x509Response{StatusCode: http.StatusOK}, |
486 | + errorObject: nil} |
487 | + responses := []DispatcherResponse{firstResponse, secondResponse} |
488 | + rigPreparedResponseDispatcher(responses) |
489 | + recordedRequests := make([]*X509Request, 0) |
490 | + rigRecordingDispatcher(&recordedRequests) |
491 | + |
492 | + api := makeAPI(c) |
493 | + diskName := "gwacldiske5w7lkj" |
494 | + poller := diskDeletePoller{api, diskName, false} |
495 | + |
496 | + response, err := performPolling(poller, time.Nanosecond, time.Minute) |
497 | + |
498 | + c.Assert(response, IsNil) |
499 | + c.Assert(err, IsNil) |
500 | + expectedURL := api.session.composeURL("services/disks/" + diskName) |
501 | + c.Check(len(recordedRequests), Equals, 2) |
502 | + checkRequest(c, recordedRequests[0], expectedURL, "2012-08-01", nil, "DELETE") |
503 | + checkRequest(c, recordedRequests[1], expectedURL, "2012-08-01", nil, "DELETE") |
504 | } |
505 | |
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 | package gwacl |
511 | |
512 | import ( |
513 | - "fmt" |
514 | - "net/url" |
515 | - "strings" |
516 | + "fmt" |
517 | + "net/url" |
518 | + "strings" |
519 | ) |
520 | |
521 | // APIEndpoint describes the base URL for accesing Windows Azure's APIs. |
522 | @@ -20,13 +20,13 @@ |
523 | // GetEndpoint returns the API endpoint for the given location. This is |
524 | // hard-coded, so some guesswork may be involved. |
525 | func GetEndpoint(location string) APIEndpoint { |
526 | - if strings.Contains(location, "China") { |
527 | - // Mainland China is a special case. It has its own endpoint. |
528 | - return "https://core.chinacloudapi.cn/" |
529 | - } |
530 | + if strings.Contains(location, "China") { |
531 | + // Mainland China is a special case. It has its own endpoint. |
532 | + return "https://core.chinacloudapi.cn/" |
533 | + } |
534 | |
535 | - // The rest of the world shares a single endpoint. |
536 | - return "https://core.windows.net/" |
537 | + // The rest of the world shares a single endpoint. |
538 | + return "https://core.windows.net/" |
539 | } |
540 | |
541 | // prefixHost prefixes the hostname part of a URL with a subdomain. For |
542 | @@ -35,26 +35,26 @@ |
543 | // |
544 | // The URL must be well-formed, and contain a hostname. |
545 | func prefixHost(host, originalURL string) string { |
546 | - parsedURL, err := url.Parse(originalURL) |
547 | - if err != nil { |
548 | - panic(fmt.Errorf("failed to parse URL %s - %v", originalURL, err)) |
549 | - } |
550 | - if parsedURL.Host == "" { |
551 | - panic(fmt.Errorf("no hostname in URL '%s'", originalURL)) |
552 | - } |
553 | - // Escape manually. Strangely, turning a url.URL into a string does not |
554 | - // do this for you. |
555 | - parsedURL.Host = url.QueryEscape(host) + "." + parsedURL.Host |
556 | - return parsedURL.String() |
557 | + parsedURL, err := url.Parse(originalURL) |
558 | + if err != nil { |
559 | + panic(fmt.Errorf("failed to parse URL %s - %v", originalURL, err)) |
560 | + } |
561 | + if parsedURL.Host == "" { |
562 | + panic(fmt.Errorf("no hostname in URL '%s'", originalURL)) |
563 | + } |
564 | + // Escape manually. Strangely, turning a url.URL into a string does not |
565 | + // do this for you. |
566 | + parsedURL.Host = url.QueryEscape(host) + "." + parsedURL.Host |
567 | + return parsedURL.String() |
568 | } |
569 | |
570 | // ManagementAPI returns the URL for the endpoint's management API. |
571 | func (endpoint APIEndpoint) ManagementAPI() string { |
572 | - return prefixHost("management", string(endpoint)) |
573 | + return prefixHost("management", string(endpoint)) |
574 | } |
575 | |
576 | // BlobStorageAPI returns a URL for the endpoint's blob storage API, for |
577 | // requests on the given account. |
578 | func (endpoint APIEndpoint) BlobStorageAPI(account string) string { |
579 | - return prefixHost(account, prefixHost("blob", string(endpoint))) |
580 | + return prefixHost(account, prefixHost("blob", string(endpoint))) |
581 | } |
582 | |
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 | package gwacl |
588 | |
589 | import ( |
590 | - "fmt" |
591 | - . "launchpad.net/gocheck" |
592 | - "net/url" |
593 | + "fmt" |
594 | + . "launchpad.net/gocheck" |
595 | + "net/url" |
596 | ) |
597 | |
598 | type endpointsSuite struct{} |
599 | @@ -14,109 +14,109 @@ |
600 | var _ = Suite(&endpointsSuite{}) |
601 | |
602 | func (*endpointsSuite) TestGetEndpointReturnsEndpointsForKnownRegions(c *C) { |
603 | - internationalLocations := []string{ |
604 | - "West Europe", |
605 | - "East Asia", |
606 | - "East US 2", |
607 | - "Southeast Asia", |
608 | - "East US", |
609 | - "Central US", |
610 | - "West US", |
611 | - "North Europe", |
612 | - } |
613 | - internationalEndpoint := APIEndpoint("https://core.windows.net/") |
614 | - |
615 | - for _, location := range internationalLocations { |
616 | - c.Check(GetEndpoint(location), Equals, internationalEndpoint) |
617 | - } |
618 | - |
619 | - // The mainland-China locations have a different endpoint. |
620 | - // (Actually the East Asia data centre is said to be in Hong Kong, but it |
621 | - // acts as international). |
622 | - mainlandChinaLocations := []string{ |
623 | - "China East", |
624 | - "China North", |
625 | - } |
626 | - mainlandChinaEndpoint := APIEndpoint("https://core.chinacloudapi.cn/") |
627 | - for _, location := range mainlandChinaLocations { |
628 | - c.Check(GetEndpoint(location), Equals, mainlandChinaEndpoint) |
629 | - } |
630 | + internationalLocations := []string{ |
631 | + "West Europe", |
632 | + "East Asia", |
633 | + "East US 2", |
634 | + "Southeast Asia", |
635 | + "East US", |
636 | + "Central US", |
637 | + "West US", |
638 | + "North Europe", |
639 | + } |
640 | + internationalEndpoint := APIEndpoint("https://core.windows.net/") |
641 | + |
642 | + for _, location := range internationalLocations { |
643 | + c.Check(GetEndpoint(location), Equals, internationalEndpoint) |
644 | + } |
645 | + |
646 | + // The mainland-China locations have a different endpoint. |
647 | + // (Actually the East Asia data centre is said to be in Hong Kong, but it |
648 | + // acts as international). |
649 | + mainlandChinaLocations := []string{ |
650 | + "China East", |
651 | + "China North", |
652 | + } |
653 | + mainlandChinaEndpoint := APIEndpoint("https://core.chinacloudapi.cn/") |
654 | + for _, location := range mainlandChinaLocations { |
655 | + c.Check(GetEndpoint(location), Equals, mainlandChinaEndpoint) |
656 | + } |
657 | } |
658 | |
659 | func (*endpointsSuite) TestGetEndpointMakesGoodGuessesForUknownRegions(c *C) { |
660 | - c.Check( |
661 | - GetEndpoint("South San Marino Highlands"), |
662 | - Equals, |
663 | - GetEndpoint("West US")) |
664 | - c.Check( |
665 | - GetEndpoint("Central China West"), |
666 | - Equals, |
667 | - GetEndpoint("China East")) |
668 | + c.Check( |
669 | + GetEndpoint("South San Marino Highlands"), |
670 | + Equals, |
671 | + GetEndpoint("West US")) |
672 | + c.Check( |
673 | + GetEndpoint("Central China West"), |
674 | + Equals, |
675 | + GetEndpoint("China East")) |
676 | } |
677 | |
678 | func (*endpointsSuite) TestPrefixHostPrefixesSubdomain(c *C) { |
679 | - c.Check( |
680 | - prefixHost("foo", "http://example.com"), |
681 | - Equals, |
682 | - "http://foo.example.com") |
683 | + c.Check( |
684 | + prefixHost("foo", "http://example.com"), |
685 | + Equals, |
686 | + "http://foo.example.com") |
687 | } |
688 | |
689 | func (*endpointsSuite) TestPrefixHostPreservesOtherURLComponents(c *C) { |
690 | - c.Check( |
691 | - prefixHost("foo", "http://example.com/"), |
692 | - Equals, |
693 | - "http://foo.example.com/") |
694 | - c.Check( |
695 | - prefixHost("foo", "nntp://example.com"), |
696 | - Equals, |
697 | - "nntp://foo.example.com") |
698 | - c.Check( |
699 | - prefixHost("foo", "http://user@example.com"), |
700 | - Equals, |
701 | - "http://user@foo.example.com") |
702 | - c.Check( |
703 | - prefixHost("foo", "http://example.com:999"), |
704 | - Equals, |
705 | - "http://foo.example.com:999") |
706 | - c.Check( |
707 | - prefixHost("foo", "http://example.com/path"), |
708 | - Equals, |
709 | - "http://foo.example.com/path") |
710 | + c.Check( |
711 | + prefixHost("foo", "http://example.com/"), |
712 | + Equals, |
713 | + "http://foo.example.com/") |
714 | + c.Check( |
715 | + prefixHost("foo", "nntp://example.com"), |
716 | + Equals, |
717 | + "nntp://foo.example.com") |
718 | + c.Check( |
719 | + prefixHost("foo", "http://user@example.com"), |
720 | + Equals, |
721 | + "http://user@foo.example.com") |
722 | + c.Check( |
723 | + prefixHost("foo", "http://example.com:999"), |
724 | + Equals, |
725 | + "http://foo.example.com:999") |
726 | + c.Check( |
727 | + prefixHost("foo", "http://example.com/path"), |
728 | + Equals, |
729 | + "http://foo.example.com/path") |
730 | } |
731 | |
732 | func (*endpointsSuite) TestPrefixHostEscapes(c *C) { |
733 | - host := "5%=1/20?" |
734 | - c.Check( |
735 | - prefixHost(host, "http://example.com"), |
736 | - Equals, |
737 | - fmt.Sprintf("http://%s.example.com", url.QueryEscape(host))) |
738 | + host := "5%=1/20?" |
739 | + c.Check( |
740 | + prefixHost(host, "http://example.com"), |
741 | + Equals, |
742 | + fmt.Sprintf("http://%s.example.com", url.QueryEscape(host))) |
743 | } |
744 | |
745 | func (*endpointsSuite) TestManagementAPICombinesWithGetEndpoint(c *C) { |
746 | - c.Check( |
747 | - GetEndpoint("West US").ManagementAPI(), |
748 | - Equals, |
749 | - "https://management.core.windows.net/") |
750 | - c.Check( |
751 | - GetEndpoint("China East").ManagementAPI(), |
752 | - Equals, |
753 | - "https://management.core.chinacloudapi.cn/") |
754 | + c.Check( |
755 | + GetEndpoint("West US").ManagementAPI(), |
756 | + Equals, |
757 | + "https://management.core.windows.net/") |
758 | + c.Check( |
759 | + GetEndpoint("China East").ManagementAPI(), |
760 | + Equals, |
761 | + "https://management.core.chinacloudapi.cn/") |
762 | } |
763 | |
764 | func (*endpointsSuite) TestBlobStorageAPIIncludesAccountName(c *C) { |
765 | - c.Check( |
766 | - APIEndpoint("http://example.com").BlobStorageAPI("myaccount"), |
767 | - Equals, |
768 | - "http://myaccount.blob.example.com") |
769 | + c.Check( |
770 | + APIEndpoint("http://example.com").BlobStorageAPI("myaccount"), |
771 | + Equals, |
772 | + "http://myaccount.blob.example.com") |
773 | } |
774 | |
775 | func (*endpointsSuite) TestBlobStorageAPICombinesWithGetEndpoint(c *C) { |
776 | - c.Check( |
777 | - GetEndpoint("West US").BlobStorageAPI("account"), |
778 | - Equals, |
779 | - "https://account.blob.core.windows.net/") |
780 | - c.Check( |
781 | - GetEndpoint("China East").BlobStorageAPI("account"), |
782 | - Equals, |
783 | - "https://account.blob.core.chinacloudapi.cn/") |
784 | + c.Check( |
785 | + GetEndpoint("West US").BlobStorageAPI("account"), |
786 | + Equals, |
787 | + "https://account.blob.core.windows.net/") |
788 | + c.Check( |
789 | + GetEndpoint("China East").BlobStorageAPI("account"), |
790 | + Equals, |
791 | + "https://account.blob.core.chinacloudapi.cn/") |
792 | } |
793 | |
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 | package main |
799 | |
800 | import ( |
801 | - "encoding/base64" |
802 | - "flag" |
803 | - "fmt" |
804 | - "launchpad.net/gwacl" |
805 | - . "launchpad.net/gwacl/logging" |
806 | - "math/rand" |
807 | - "os" |
808 | - "time" |
809 | + "encoding/base64" |
810 | + "flag" |
811 | + "fmt" |
812 | + "launchpad.net/gwacl" |
813 | + . "launchpad.net/gwacl/logging" |
814 | + "math/rand" |
815 | + "os" |
816 | + "time" |
817 | ) |
818 | |
819 | var certFile string |
820 | @@ -26,26 +26,26 @@ |
821 | var location string |
822 | |
823 | func getParams() error { |
824 | - flag.StringVar(&certFile, "cert", "", "Name of your management certificate file (in PEM format).") |
825 | - flag.StringVar(&subscriptionID, "subscriptionid", "", "Your Azure subscription ID.") |
826 | - flag.BoolVar(&pause, "pause", false, "Wait for user input after the VM is brought up (useful for further testing)") |
827 | - flag.StringVar(&location, "location", "North Europe", "Azure cloud location, e.g. 'West US' or 'China East'") |
828 | - |
829 | - flag.Parse() |
830 | - |
831 | - if certFile == "" { |
832 | - return fmt.Errorf("No .pem certificate specified. Use the -cert option.") |
833 | - } |
834 | - if subscriptionID == "" { |
835 | - return fmt.Errorf("No subscription ID specified. Use the -subscriptionid option.") |
836 | - } |
837 | - return nil |
838 | + flag.StringVar(&certFile, "cert", "", "Name of your management certificate file (in PEM format).") |
839 | + flag.StringVar(&subscriptionID, "subscriptionid", "", "Your Azure subscription ID.") |
840 | + flag.BoolVar(&pause, "pause", false, "Wait for user input after the VM is brought up (useful for further testing)") |
841 | + flag.StringVar(&location, "location", "North Europe", "Azure cloud location, e.g. 'West US' or 'China East'") |
842 | + |
843 | + flag.Parse() |
844 | + |
845 | + if certFile == "" { |
846 | + return fmt.Errorf("No .pem certificate specified. Use the -cert option.") |
847 | + } |
848 | + if subscriptionID == "" { |
849 | + return fmt.Errorf("No subscription ID specified. Use the -subscriptionid option.") |
850 | + } |
851 | + return nil |
852 | } |
853 | |
854 | func checkError(err error) { |
855 | - if err != nil { |
856 | - panic(err) |
857 | - } |
858 | + if err != nil { |
859 | + panic(err) |
860 | + } |
861 | } |
862 | |
863 | // makeRandomIdentifier creates an arbitrary identifier of the given length, |
864 | @@ -53,231 +53,231 @@ |
865 | // The identifier will start with the given prefix. The prefix must be no |
866 | // longer than the specified length, or there'll be trouble. |
867 | func makeRandomIdentifier(prefix string, length int) string { |
868 | - // Only digits and lower-case ASCII letters are accepted. |
869 | - const chars = "abcdefghijklmnopqrstuvwxyz0123456789" |
870 | - |
871 | - if len(prefix) > length { |
872 | - panic(fmt.Errorf("prefix '%s' is more than the requested %d characters long", prefix, length)) |
873 | - } |
874 | - |
875 | - id := prefix |
876 | - for len(id) < length { |
877 | - id += string(chars[rand.Intn(len(chars))]) |
878 | - } |
879 | - return id |
880 | + // Only digits and lower-case ASCII letters are accepted. |
881 | + const chars = "abcdefghijklmnopqrstuvwxyz0123456789" |
882 | + |
883 | + if len(prefix) > length { |
884 | + panic(fmt.Errorf("prefix '%s' is more than the requested %d characters long", prefix, length)) |
885 | + } |
886 | + |
887 | + id := prefix |
888 | + for len(id) < length { |
889 | + id += string(chars[rand.Intn(len(chars))]) |
890 | + } |
891 | + return id |
892 | } |
893 | |
894 | func main() { |
895 | - rand.Seed(int64(time.Now().Nanosecond())) |
896 | - |
897 | - err := getParams() |
898 | - if err != nil { |
899 | - Info(err) |
900 | - os.Exit(1) |
901 | - } |
902 | - |
903 | - api, err := gwacl.NewManagementAPI(subscriptionID, certFile, location) |
904 | - checkError(err) |
905 | - |
906 | - ExerciseHostedServicesAPI(api) |
907 | - |
908 | - Info("All done.") |
909 | + rand.Seed(int64(time.Now().Nanosecond())) |
910 | + |
911 | + err := getParams() |
912 | + if err != nil { |
913 | + Info(err) |
914 | + os.Exit(1) |
915 | + } |
916 | + |
917 | + api, err := gwacl.NewManagementAPI(subscriptionID, certFile, location) |
918 | + checkError(err) |
919 | + |
920 | + ExerciseHostedServicesAPI(api) |
921 | + |
922 | + Info("All done.") |
923 | } |
924 | |
925 | func ExerciseHostedServicesAPI(api *gwacl.ManagementAPI) { |
926 | - var err error |
927 | - location := "West US" |
928 | - release := "13.04" |
929 | - |
930 | - affinityGroupName := gwacl.MakeRandomHostname("affinitygroup") |
931 | - Info("Creating an affinity group...") |
932 | - cag := gwacl.NewCreateAffinityGroup(affinityGroupName, "affinity-label", "affinity-description", location) |
933 | - err = api.CreateAffinityGroup(&gwacl.CreateAffinityGroupRequest{ |
934 | - CreateAffinityGroup: cag}) |
935 | - checkError(err) |
936 | - Infof("Created affinity group %s\n", affinityGroupName) |
937 | - |
938 | - defer func() { |
939 | - Infof("Deleting affinity group %s\n", affinityGroupName) |
940 | - err := api.DeleteAffinityGroup(&gwacl.DeleteAffinityGroupRequest{ |
941 | - Name: affinityGroupName}) |
942 | - checkError(err) |
943 | - Infof("Done deleting affinity group %s\n", affinityGroupName) |
944 | - }() |
945 | - |
946 | - virtualNetworkName := gwacl.MakeRandomVirtualNetworkName("virtual-net-") |
947 | - Infof("Creating virtual network %s...\n", virtualNetworkName) |
948 | - virtualNetwork := gwacl.VirtualNetworkSite{ |
949 | - Name: virtualNetworkName, |
950 | - AffinityGroup: affinityGroupName, |
951 | - AddressSpacePrefixes: []string{ |
952 | - "10.0.0.0/8", |
953 | - }, |
954 | - } |
955 | - err = api.AddVirtualNetworkSite(&virtualNetwork) |
956 | - checkError(err) |
957 | - Info("Done creating virtual network") |
958 | - |
959 | - defer func() { |
960 | - Infof("Deleting virtual network %s...\n", virtualNetworkName) |
961 | - err := api.RemoveVirtualNetworkSite(virtualNetworkName) |
962 | - checkError(err) |
963 | - Infof("Done deleting virtual network %s\n", virtualNetworkName) |
964 | - }() |
965 | - |
966 | - networkConfig, err := api.GetNetworkConfiguration() |
967 | - checkError(err) |
968 | - if networkConfig == nil { |
969 | - Info("No network configuration is set") |
970 | - } else { |
971 | - xml, err := networkConfig.Serialize() |
972 | - checkError(err) |
973 | - Info(xml) |
974 | - } |
975 | - |
976 | - Infof("Getting OS Image for release '%s' and location '%s'...\n", release, location) |
977 | - images, err := api.ListOSImages() |
978 | - checkError(err) |
979 | - image, err := images.GetLatestUbuntuImage(release, location) |
980 | - checkError(err) |
981 | - sourceImageName := image.Name |
982 | - Infof("Got image named '%s'\n", sourceImageName) |
983 | - Info("Done getting OS Image\n") |
984 | - |
985 | - hostServiceName := gwacl.MakeRandomHostedServiceName("gwacl") |
986 | - Infof("Creating a hosted service: '%s'...\n", hostServiceName) |
987 | - createHostedService := gwacl.NewCreateHostedServiceWithLocation(hostServiceName, "testLabel", location) |
988 | - createHostedService.AffinityGroup = affinityGroupName |
989 | - err = api.AddHostedService(createHostedService) |
990 | - checkError(err) |
991 | - Info("Done creating a hosted service\n") |
992 | - |
993 | - defer func() { |
994 | - Info("Destroying hosted service...") |
995 | - // FIXME: Check error |
996 | - api.DestroyHostedService(&gwacl.DestroyHostedServiceRequest{ |
997 | - ServiceName: hostServiceName}) |
998 | - Info("Done destroying hosted service\n") |
999 | - }() |
1000 | - |
1001 | - Info("Listing hosted services...") |
1002 | - hostedServices, err := api.ListHostedServices() |
1003 | - checkError(err) |
1004 | - Infof("Got %d hosted service(s)\n", len(hostedServices)) |
1005 | - if len(hostedServices) > 0 { |
1006 | - hostedService := hostedServices[0] |
1007 | - detailedHostedService, err := api.GetHostedServiceProperties(hostedService.ServiceName, true) |
1008 | - checkError(err) |
1009 | - Infof("Hosted service '%s' contains %d deployments\n", hostedService.ServiceName, len(detailedHostedService.Deployments)) |
1010 | - // Do the same again with ListAllDeployments. |
1011 | - deployments, err := api.ListAllDeployments(&gwacl.ListAllDeploymentsRequest{ServiceName: hostedService.ServiceName}) |
1012 | - checkError(err) |
1013 | - if len(deployments) != len(detailedHostedService.Deployments) { |
1014 | - Errorf( |
1015 | - "Mismatch in reported deployments: %d != %d", |
1016 | - len(deployments), len(detailedHostedService.Deployments)) |
1017 | - } |
1018 | - } |
1019 | - Info("Done listing hosted services\n") |
1020 | - |
1021 | - Info("Adding VM deployment...") |
1022 | - hostname := gwacl.MakeRandomHostname("gwaclhost") |
1023 | - // Random passwords are no use to man nor beast here if you want to |
1024 | - // test with your instance, so we'll use a fixed one. It's not really a |
1025 | - // security hazard in such a short-lived private instance. |
1026 | - password := "Ubuntu123" |
1027 | - username := "ubuntu" |
1028 | - vhdName := gwacl.MakeRandomDiskName("gwacldisk") |
1029 | - userdata := base64.StdEncoding.EncodeToString([]byte("TEST_USER_DATA")) |
1030 | - linuxConfigurationSet := gwacl.NewLinuxProvisioningConfigurationSet( |
1031 | - hostname, username, password, userdata, "false") |
1032 | - inputendpoint := gwacl.InputEndpoint{LocalPort: 22, Name: "sshport", Port: 22, Protocol: "TCP"} |
1033 | - networkConfigurationSet := gwacl.NewNetworkConfigurationSet([]gwacl.InputEndpoint{inputendpoint}, nil) |
1034 | - |
1035 | - storageAccount := makeRandomIdentifier("gwacl", 24) |
1036 | - storageLabel := makeRandomIdentifier("gwacl", 64) |
1037 | - Infof("Requesting storage account with name '%s' and label '%s'...\n", storageAccount, storageLabel) |
1038 | - cssi := gwacl.NewCreateStorageServiceInputWithLocation(storageAccount, storageLabel, location, "false") |
1039 | - err = api.AddStorageAccount(cssi) |
1040 | - checkError(err) |
1041 | - Info("Done requesting storage account\n") |
1042 | - |
1043 | - defer func() { |
1044 | - Infof("Deleting storage account %s...\n", storageAccount) |
1045 | - // FIXME: Check error |
1046 | - api.DeleteStorageAccount(storageAccount) |
1047 | - Info("Done deleting storage account\n") |
1048 | - }() |
1049 | - |
1050 | - mediaLink := gwacl.CreateVirtualHardDiskMediaLink(storageAccount, fmt.Sprintf("vhds/%s.vhd", vhdName)) |
1051 | - diskName := makeRandomIdentifier("gwacldisk", 16) |
1052 | - diskLabel := makeRandomIdentifier("gwacl", 64) |
1053 | - vhd := gwacl.NewOSVirtualHardDisk("", diskLabel, diskName, mediaLink, sourceImageName, "Linux") |
1054 | - roleName := gwacl.MakeRandomRoleName("gwaclrole") |
1055 | - role := gwacl.NewRole("ExtraSmall", roleName, vhd, |
1056 | - []gwacl.ConfigurationSet{*linuxConfigurationSet, *networkConfigurationSet}) |
1057 | - machineName := makeRandomIdentifier("gwaclmachine", 20) |
1058 | - deployment := gwacl.NewDeploymentForCreateVMDeployment( |
1059 | - machineName, "Production", machineName, []gwacl.Role{*role}, virtualNetworkName) |
1060 | - err = api.AddDeployment(deployment, hostServiceName) |
1061 | - checkError(err) |
1062 | - Info("Done adding VM deployment\n") |
1063 | - |
1064 | - Info("Starting VM...") |
1065 | - err = api.StartRole(&gwacl.StartRoleRequest{hostServiceName, deployment.Name, role.RoleName}) |
1066 | - checkError(err) |
1067 | - Info("Done starting VM\n") |
1068 | - |
1069 | - Info("Listing VM...") |
1070 | - instances, err := api.ListInstances(&gwacl.ListInstancesRequest{hostServiceName}) |
1071 | - checkError(err) |
1072 | - Infof("Got %d instance(s)\n", len(instances)) |
1073 | - Info("Done listing VM\n") |
1074 | - |
1075 | - Info("Getting deployment info...") |
1076 | - request := &gwacl.GetDeploymentRequest{ServiceName: hostServiceName, DeploymentName: machineName} |
1077 | - deploy, err := api.GetDeployment(request) |
1078 | - checkError(err) |
1079 | - fqdn, err := deploy.GetFQDN() |
1080 | - checkError(err) |
1081 | - Info("Got deployment info\n") |
1082 | - |
1083 | - Info("Adding role input endpoint...") |
1084 | - endpoint := gwacl.InputEndpoint{ |
1085 | - Name: gwacl.MakeRandomHostname("endpoint-"), |
1086 | - Port: 1080, |
1087 | - LocalPort: 80, |
1088 | - Protocol: "TCP", |
1089 | - } |
1090 | - err = api.AddRoleEndpoints(&gwacl.AddRoleEndpointsRequest{ |
1091 | - ServiceName: hostServiceName, |
1092 | - DeploymentName: deployment.Name, |
1093 | - RoleName: role.RoleName, |
1094 | - InputEndpoints: []gwacl.InputEndpoint{endpoint}, |
1095 | - }) |
1096 | - checkError(err) |
1097 | - Info("Added role input endpoint\n") |
1098 | - |
1099 | - defer func() { |
1100 | - Info("Removing role input endpoint...") |
1101 | - err := api.RemoveRoleEndpoints(&gwacl.RemoveRoleEndpointsRequest{ |
1102 | - ServiceName: hostServiceName, |
1103 | - DeploymentName: deployment.Name, |
1104 | - RoleName: role.RoleName, |
1105 | - InputEndpoints: []gwacl.InputEndpoint{endpoint}, |
1106 | - }) |
1107 | - checkError(err) |
1108 | - Info("Removed role input endpoint\n") |
1109 | - }() |
1110 | - |
1111 | - if pause { |
1112 | - var wait string |
1113 | - fmt.Println("host:", fqdn) |
1114 | - fmt.Println("username:", username) |
1115 | - fmt.Println("password:", password) |
1116 | - fmt.Println("") |
1117 | - fmt.Println("Pausing so you can play with the newly-created VM") |
1118 | - fmt.Println("To clear up, type something and press enter to continue") |
1119 | - fmt.Scan(&wait) |
1120 | - } |
1121 | + var err error |
1122 | + location := "West US" |
1123 | + release := "13.04" |
1124 | + |
1125 | + affinityGroupName := gwacl.MakeRandomHostname("affinitygroup") |
1126 | + Info("Creating an affinity group...") |
1127 | + cag := gwacl.NewCreateAffinityGroup(affinityGroupName, "affinity-label", "affinity-description", location) |
1128 | + err = api.CreateAffinityGroup(&gwacl.CreateAffinityGroupRequest{ |
1129 | + CreateAffinityGroup: cag}) |
1130 | + checkError(err) |
1131 | + Infof("Created affinity group %s\n", affinityGroupName) |
1132 | + |
1133 | + defer func() { |
1134 | + Infof("Deleting affinity group %s\n", affinityGroupName) |
1135 | + err := api.DeleteAffinityGroup(&gwacl.DeleteAffinityGroupRequest{ |
1136 | + Name: affinityGroupName}) |
1137 | + checkError(err) |
1138 | + Infof("Done deleting affinity group %s\n", affinityGroupName) |
1139 | + }() |
1140 | + |
1141 | + virtualNetworkName := gwacl.MakeRandomVirtualNetworkName("virtual-net-") |
1142 | + Infof("Creating virtual network %s...\n", virtualNetworkName) |
1143 | + virtualNetwork := gwacl.VirtualNetworkSite{ |
1144 | + Name: virtualNetworkName, |
1145 | + AffinityGroup: affinityGroupName, |
1146 | + AddressSpacePrefixes: []string{ |
1147 | + "10.0.0.0/8", |
1148 | + }, |
1149 | + } |
1150 | + err = api.AddVirtualNetworkSite(&virtualNetwork) |
1151 | + checkError(err) |
1152 | + Info("Done creating virtual network") |
1153 | + |
1154 | + defer func() { |
1155 | + Infof("Deleting virtual network %s...\n", virtualNetworkName) |
1156 | + err := api.RemoveVirtualNetworkSite(virtualNetworkName) |
1157 | + checkError(err) |
1158 | + Infof("Done deleting virtual network %s\n", virtualNetworkName) |
1159 | + }() |
1160 | + |
1161 | + networkConfig, err := api.GetNetworkConfiguration() |
1162 | + checkError(err) |
1163 | + if networkConfig == nil { |
1164 | + Info("No network configuration is set") |
1165 | + } else { |
1166 | + xml, err := networkConfig.Serialize() |
1167 | + checkError(err) |
1168 | + Info(xml) |
1169 | + } |
1170 | + |
1171 | + Infof("Getting OS Image for release '%s' and location '%s'...\n", release, location) |
1172 | + images, err := api.ListOSImages() |
1173 | + checkError(err) |
1174 | + image, err := images.GetLatestUbuntuImage(release, location) |
1175 | + checkError(err) |
1176 | + sourceImageName := image.Name |
1177 | + Infof("Got image named '%s'\n", sourceImageName) |
1178 | + Info("Done getting OS Image\n") |
1179 | + |
1180 | + hostServiceName := gwacl.MakeRandomHostedServiceName("gwacl") |
1181 | + Infof("Creating a hosted service: '%s'...\n", hostServiceName) |
1182 | + createHostedService := gwacl.NewCreateHostedServiceWithLocation(hostServiceName, "testLabel", location) |
1183 | + createHostedService.AffinityGroup = affinityGroupName |
1184 | + err = api.AddHostedService(createHostedService) |
1185 | + checkError(err) |
1186 | + Info("Done creating a hosted service\n") |
1187 | + |
1188 | + defer func() { |
1189 | + Info("Destroying hosted service...") |
1190 | + // FIXME: Check error |
1191 | + api.DestroyHostedService(&gwacl.DestroyHostedServiceRequest{ |
1192 | + ServiceName: hostServiceName}) |
1193 | + Info("Done destroying hosted service\n") |
1194 | + }() |
1195 | + |
1196 | + Info("Listing hosted services...") |
1197 | + hostedServices, err := api.ListHostedServices() |
1198 | + checkError(err) |
1199 | + Infof("Got %d hosted service(s)\n", len(hostedServices)) |
1200 | + if len(hostedServices) > 0 { |
1201 | + hostedService := hostedServices[0] |
1202 | + detailedHostedService, err := api.GetHostedServiceProperties(hostedService.ServiceName, true) |
1203 | + checkError(err) |
1204 | + Infof("Hosted service '%s' contains %d deployments\n", hostedService.ServiceName, len(detailedHostedService.Deployments)) |
1205 | + // Do the same again with ListAllDeployments. |
1206 | + deployments, err := api.ListAllDeployments(&gwacl.ListAllDeploymentsRequest{ServiceName: hostedService.ServiceName}) |
1207 | + checkError(err) |
1208 | + if len(deployments) != len(detailedHostedService.Deployments) { |
1209 | + Errorf( |
1210 | + "Mismatch in reported deployments: %d != %d", |
1211 | + len(deployments), len(detailedHostedService.Deployments)) |
1212 | + } |
1213 | + } |
1214 | + Info("Done listing hosted services\n") |
1215 | + |
1216 | + Info("Adding VM deployment...") |
1217 | + hostname := gwacl.MakeRandomHostname("gwaclhost") |
1218 | + // Random passwords are no use to man nor beast here if you want to |
1219 | + // test with your instance, so we'll use a fixed one. It's not really a |
1220 | + // security hazard in such a short-lived private instance. |
1221 | + password := "Ubuntu123" |
1222 | + username := "ubuntu" |
1223 | + vhdName := gwacl.MakeRandomDiskName("gwacldisk") |
1224 | + userdata := base64.StdEncoding.EncodeToString([]byte("TEST_USER_DATA")) |
1225 | + linuxConfigurationSet := gwacl.NewLinuxProvisioningConfigurationSet( |
1226 | + hostname, username, password, userdata, "false") |
1227 | + inputendpoint := gwacl.InputEndpoint{LocalPort: 22, Name: "sshport", Port: 22, Protocol: "TCP"} |
1228 | + networkConfigurationSet := gwacl.NewNetworkConfigurationSet([]gwacl.InputEndpoint{inputendpoint}, nil) |
1229 | + |
1230 | + storageAccount := makeRandomIdentifier("gwacl", 24) |
1231 | + storageLabel := makeRandomIdentifier("gwacl", 64) |
1232 | + Infof("Requesting storage account with name '%s' and label '%s'...\n", storageAccount, storageLabel) |
1233 | + cssi := gwacl.NewCreateStorageServiceInputWithLocation(storageAccount, storageLabel, location, "false") |
1234 | + err = api.AddStorageAccount(cssi) |
1235 | + checkError(err) |
1236 | + Info("Done requesting storage account\n") |
1237 | + |
1238 | + defer func() { |
1239 | + Infof("Deleting storage account %s...\n", storageAccount) |
1240 | + // FIXME: Check error |
1241 | + api.DeleteStorageAccount(storageAccount) |
1242 | + Info("Done deleting storage account\n") |
1243 | + }() |
1244 | + |
1245 | + mediaLink := gwacl.CreateVirtualHardDiskMediaLink(storageAccount, fmt.Sprintf("vhds/%s.vhd", vhdName)) |
1246 | + diskName := makeRandomIdentifier("gwacldisk", 16) |
1247 | + diskLabel := makeRandomIdentifier("gwacl", 64) |
1248 | + vhd := gwacl.NewOSVirtualHardDisk("", diskLabel, diskName, mediaLink, sourceImageName, "Linux") |
1249 | + roleName := gwacl.MakeRandomRoleName("gwaclrole") |
1250 | + role := gwacl.NewRole("ExtraSmall", roleName, vhd, |
1251 | + []gwacl.ConfigurationSet{*linuxConfigurationSet, *networkConfigurationSet}) |
1252 | + machineName := makeRandomIdentifier("gwaclmachine", 20) |
1253 | + deployment := gwacl.NewDeploymentForCreateVMDeployment( |
1254 | + machineName, "Production", machineName, []gwacl.Role{*role}, virtualNetworkName) |
1255 | + err = api.AddDeployment(deployment, hostServiceName) |
1256 | + checkError(err) |
1257 | + Info("Done adding VM deployment\n") |
1258 | + |
1259 | + Info("Starting VM...") |
1260 | + err = api.StartRole(&gwacl.StartRoleRequest{hostServiceName, deployment.Name, role.RoleName}) |
1261 | + checkError(err) |
1262 | + Info("Done starting VM\n") |
1263 | + |
1264 | + Info("Listing VM...") |
1265 | + instances, err := api.ListInstances(&gwacl.ListInstancesRequest{hostServiceName}) |
1266 | + checkError(err) |
1267 | + Infof("Got %d instance(s)\n", len(instances)) |
1268 | + Info("Done listing VM\n") |
1269 | + |
1270 | + Info("Getting deployment info...") |
1271 | + request := &gwacl.GetDeploymentRequest{ServiceName: hostServiceName, DeploymentName: machineName} |
1272 | + deploy, err := api.GetDeployment(request) |
1273 | + checkError(err) |
1274 | + fqdn, err := deploy.GetFQDN() |
1275 | + checkError(err) |
1276 | + Info("Got deployment info\n") |
1277 | + |
1278 | + Info("Adding role input endpoint...") |
1279 | + endpoint := gwacl.InputEndpoint{ |
1280 | + Name: gwacl.MakeRandomHostname("endpoint-"), |
1281 | + Port: 1080, |
1282 | + LocalPort: 80, |
1283 | + Protocol: "TCP", |
1284 | + } |
1285 | + err = api.AddRoleEndpoints(&gwacl.AddRoleEndpointsRequest{ |
1286 | + ServiceName: hostServiceName, |
1287 | + DeploymentName: deployment.Name, |
1288 | + RoleName: role.RoleName, |
1289 | + InputEndpoints: []gwacl.InputEndpoint{endpoint}, |
1290 | + }) |
1291 | + checkError(err) |
1292 | + Info("Added role input endpoint\n") |
1293 | + |
1294 | + defer func() { |
1295 | + Info("Removing role input endpoint...") |
1296 | + err := api.RemoveRoleEndpoints(&gwacl.RemoveRoleEndpointsRequest{ |
1297 | + ServiceName: hostServiceName, |
1298 | + DeploymentName: deployment.Name, |
1299 | + RoleName: role.RoleName, |
1300 | + InputEndpoints: []gwacl.InputEndpoint{endpoint}, |
1301 | + }) |
1302 | + checkError(err) |
1303 | + Info("Removed role input endpoint\n") |
1304 | + }() |
1305 | + |
1306 | + if pause { |
1307 | + var wait string |
1308 | + fmt.Println("host:", fqdn) |
1309 | + fmt.Println("username:", username) |
1310 | + fmt.Println("password:", password) |
1311 | + fmt.Println("") |
1312 | + fmt.Println("Pausing so you can play with the newly-created VM") |
1313 | + fmt.Println("To clear up, type something and press enter to continue") |
1314 | + fmt.Scan(&wait) |
1315 | + } |
1316 | } |
1317 | |
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 | package main |
1323 | |
1324 | import ( |
1325 | - "flag" |
1326 | - "fmt" |
1327 | - "io/ioutil" |
1328 | - "launchpad.net/gwacl" |
1329 | - "os" |
1330 | - "strings" |
1331 | + "flag" |
1332 | + "fmt" |
1333 | + "io/ioutil" |
1334 | + "launchpad.net/gwacl" |
1335 | + "os" |
1336 | + "strings" |
1337 | ) |
1338 | |
1339 | func badOperationError() error { |
1340 | - return fmt.Errorf("Must specify one of %v", operationNames) |
1341 | + return fmt.Errorf("Must specify one of %v", operationNames) |
1342 | } |
1343 | |
1344 | // operation is something you can instruct this program to do, by specifying |
1345 | // its name on the command line. |
1346 | type operation struct { |
1347 | - // name is the operation name as used on the command line. |
1348 | - name string |
1349 | - // description holds a description of what the operation does. |
1350 | - description string |
1351 | - // example illustrates how the operation is used. |
1352 | - example string |
1353 | - // requiredArgs lists the command-line options that are required for this |
1354 | - // operation. |
1355 | - requiredArgs []string |
1356 | - // validate is an optional callback to perform more detailed checking on |
1357 | - // the operation's arguments. |
1358 | - validate func() error |
1359 | - // implementation is a function that performs the operation. If it fails, |
1360 | - // it just panics. |
1361 | - implementation func(gwacl.StorageContext) |
1362 | + // name is the operation name as used on the command line. |
1363 | + name string |
1364 | + // description holds a description of what the operation does. |
1365 | + description string |
1366 | + // example illustrates how the operation is used. |
1367 | + example string |
1368 | + // requiredArgs lists the command-line options that are required for this |
1369 | + // operation. |
1370 | + requiredArgs []string |
1371 | + // validate is an optional callback to perform more detailed checking on |
1372 | + // the operation's arguments. |
1373 | + validate func() error |
1374 | + // implementation is a function that performs the operation. If it fails, |
1375 | + // it just panics. |
1376 | + implementation func(gwacl.StorageContext) |
1377 | } |
1378 | |
1379 | // operations defines what operations are available to be invoked from the |
1380 | // command line. |
1381 | var operations = []operation{ |
1382 | - { |
1383 | - name: "listcontainers", |
1384 | - description: "Show existing storage containers", |
1385 | - example: "listcontainers", |
1386 | - implementation: listcontainers, |
1387 | - }, |
1388 | - { |
1389 | - name: "list", |
1390 | - description: "List files in a container", |
1391 | - example: "-container=<container> list", |
1392 | - requiredArgs: []string{"container"}, |
1393 | - implementation: list, |
1394 | - }, |
1395 | - { |
1396 | - name: "containeracl", |
1397 | - description: "Set access on a container", |
1398 | - example: "-container=<container> -acl <container|blob|private> containeracl", |
1399 | - requiredArgs: []string{"container", "key", "acl"}, |
1400 | - validate: func() error { |
1401 | - if acl != "container" && acl != "blob" && acl != "private" { |
1402 | - return fmt.Errorf( |
1403 | - "Usage: containeracl -container=<container> <container|blob|private>") |
1404 | - } |
1405 | - return nil |
1406 | - }, |
1407 | - implementation: containeracl, |
1408 | - }, |
1409 | - { |
1410 | - name: "getblob", |
1411 | - description: "Get a file from a container (it's returned on stdout)", |
1412 | - example: "-container=<container> -filename=<filename> getblob", |
1413 | - requiredArgs: []string{"container", "filename"}, |
1414 | - implementation: getblob, |
1415 | - }, |
1416 | - { |
1417 | - name: "addblock", |
1418 | - description: "Upload a file to a block blob", |
1419 | - example: "-container=<container> -filename=<filename> addblock", |
1420 | - requiredArgs: []string{"key", "container", "filename"}, |
1421 | - implementation: addblock, |
1422 | - }, |
1423 | - { |
1424 | - name: "deleteblob", |
1425 | - description: "Delete a blob", |
1426 | - example: "-container=<container> -filename=<filename> deleteblob", |
1427 | - requiredArgs: []string{"key", "container", "filename"}, |
1428 | - implementation: deleteblob, |
1429 | - }, |
1430 | - { |
1431 | - name: "putblob", |
1432 | - description: "Create an empty page blob", |
1433 | - example: "-container=<container> -blobname=<blobname> -size=<bytes> " + |
1434 | - "-blobtype=\"page\" putblob", |
1435 | - requiredArgs: []string{"key", "blobname", "blobtype", "container", "size"}, |
1436 | - implementation: putblob, |
1437 | - }, |
1438 | - { |
1439 | - name: "putpage", |
1440 | - description: "Upload a file to a page blob's page. The range parameters must " + |
1441 | - "be (modulo 512)-(modulo 512 -1), eg: -pagerange=0-511", |
1442 | - example: "-container=<container> -blobname=<blobname> -pagerange=<N-N> " + |
1443 | - "-filename=<local file> putpage", |
1444 | - requiredArgs: []string{"key", "blobname", "container", "pagerange", "filename"}, |
1445 | - implementation: putpage, |
1446 | - }, |
1447 | + { |
1448 | + name: "listcontainers", |
1449 | + description: "Show existing storage containers", |
1450 | + example: "listcontainers", |
1451 | + implementation: listcontainers, |
1452 | + }, |
1453 | + { |
1454 | + name: "list", |
1455 | + description: "List files in a container", |
1456 | + example: "-container=<container> list", |
1457 | + requiredArgs: []string{"container"}, |
1458 | + implementation: list, |
1459 | + }, |
1460 | + { |
1461 | + name: "containeracl", |
1462 | + description: "Set access on a container", |
1463 | + example: "-container=<container> -acl <container|blob|private> containeracl", |
1464 | + requiredArgs: []string{"container", "key", "acl"}, |
1465 | + validate: func() error { |
1466 | + if acl != "container" && acl != "blob" && acl != "private" { |
1467 | + return fmt.Errorf( |
1468 | + "Usage: containeracl -container=<container> <container|blob|private>") |
1469 | + } |
1470 | + return nil |
1471 | + }, |
1472 | + implementation: containeracl, |
1473 | + }, |
1474 | + { |
1475 | + name: "getblob", |
1476 | + description: "Get a file from a container (it's returned on stdout)", |
1477 | + example: "-container=<container> -filename=<filename> getblob", |
1478 | + requiredArgs: []string{"container", "filename"}, |
1479 | + implementation: getblob, |
1480 | + }, |
1481 | + { |
1482 | + name: "addblock", |
1483 | + description: "Upload a file to a block blob", |
1484 | + example: "-container=<container> -filename=<filename> addblock", |
1485 | + requiredArgs: []string{"key", "container", "filename"}, |
1486 | + implementation: addblock, |
1487 | + }, |
1488 | + { |
1489 | + name: "deleteblob", |
1490 | + description: "Delete a blob", |
1491 | + example: "-container=<container> -filename=<filename> deleteblob", |
1492 | + requiredArgs: []string{"key", "container", "filename"}, |
1493 | + implementation: deleteblob, |
1494 | + }, |
1495 | + { |
1496 | + name: "putblob", |
1497 | + description: "Create an empty page blob", |
1498 | + example: "-container=<container> -blobname=<blobname> -size=<bytes> " + |
1499 | + "-blobtype=\"page\" putblob", |
1500 | + requiredArgs: []string{"key", "blobname", "blobtype", "container", "size"}, |
1501 | + implementation: putblob, |
1502 | + }, |
1503 | + { |
1504 | + name: "putpage", |
1505 | + description: "Upload a file to a page blob's page. The range parameters must " + |
1506 | + "be (modulo 512)-(modulo 512 -1), eg: -pagerange=0-511", |
1507 | + example: "-container=<container> -blobname=<blobname> -pagerange=<N-N> " + |
1508 | + "-filename=<local file> putpage", |
1509 | + requiredArgs: []string{"key", "blobname", "container", "pagerange", "filename"}, |
1510 | + implementation: putpage, |
1511 | + }, |
1512 | } |
1513 | |
1514 | // operationsByName maps each opeation name to an operation struct that |
1515 | @@ -121,130 +121,130 @@ |
1516 | var operationNames []string |
1517 | |
1518 | func init() { |
1519 | - operationsByName = make(map[string]operation, len(operations)) |
1520 | - for _, op := range operations { |
1521 | - operationsByName[op.name] = op |
1522 | - } |
1523 | + operationsByName = make(map[string]operation, len(operations)) |
1524 | + for _, op := range operations { |
1525 | + operationsByName[op.name] = op |
1526 | + } |
1527 | |
1528 | - operationNames = make([]string, len(operations)) |
1529 | - for index, op := range operations { |
1530 | - operationNames[index] = op.name |
1531 | - } |
1532 | + operationNames = make([]string, len(operations)) |
1533 | + for index, op := range operations { |
1534 | + operationNames[index] = op.name |
1535 | + } |
1536 | } |
1537 | |
1538 | // Variables set by command-line options. |
1539 | var ( |
1540 | - help bool |
1541 | - account string |
1542 | - location string |
1543 | - key string |
1544 | - filename string |
1545 | - container string |
1546 | - prefix string |
1547 | - blobname string |
1548 | - blobtype string |
1549 | - size int |
1550 | - pagerange string |
1551 | - acl string |
1552 | + help bool |
1553 | + account string |
1554 | + location string |
1555 | + key string |
1556 | + filename string |
1557 | + container string |
1558 | + prefix string |
1559 | + blobname string |
1560 | + blobtype string |
1561 | + size int |
1562 | + pagerange string |
1563 | + acl string |
1564 | ) |
1565 | |
1566 | // argumentGiven returns whether the named argument was specified on the |
1567 | // command line. |
1568 | func argumentGiven(name string) bool { |
1569 | - // This is stupid. There must be a way to ask the flag module directly! |
1570 | - switch name { |
1571 | - case "account": |
1572 | - return account != "" |
1573 | - case "location": |
1574 | - return location != "" |
1575 | - case "key": |
1576 | - return key != "" |
1577 | - case "container": |
1578 | - return container != "" |
1579 | - case "filename": |
1580 | - return filename != "" |
1581 | - case "prefix": |
1582 | - return prefix != "" |
1583 | - case "blobname": |
1584 | - return blobname != "" |
1585 | - case "blobtype": |
1586 | - return blobtype != "" |
1587 | - case "size": |
1588 | - return size != 0 |
1589 | - case "pagerange": |
1590 | - return pagerange != "" |
1591 | - case "acl": |
1592 | - return acl != "" |
1593 | - } |
1594 | - panic(fmt.Errorf("internal error: unknown command-line option: %s", name)) |
1595 | + // This is stupid. There must be a way to ask the flag module directly! |
1596 | + switch name { |
1597 | + case "account": |
1598 | + return account != "" |
1599 | + case "location": |
1600 | + return location != "" |
1601 | + case "key": |
1602 | + return key != "" |
1603 | + case "container": |
1604 | + return container != "" |
1605 | + case "filename": |
1606 | + return filename != "" |
1607 | + case "prefix": |
1608 | + return prefix != "" |
1609 | + case "blobname": |
1610 | + return blobname != "" |
1611 | + case "blobtype": |
1612 | + return blobtype != "" |
1613 | + case "size": |
1614 | + return size != 0 |
1615 | + case "pagerange": |
1616 | + return pagerange != "" |
1617 | + case "acl": |
1618 | + return acl != "" |
1619 | + } |
1620 | + panic(fmt.Errorf("internal error: unknown command-line option: %s", name)) |
1621 | } |
1622 | |
1623 | func getParams() (string, error) { |
1624 | - flag.BoolVar(&help, "h", false, "Show usage and exit") |
1625 | - |
1626 | - flag.StringVar(&account, "account", "", "Storage account name") |
1627 | - flag.StringVar(&location, "location", "", "Azure location, e.g. \"West US\", \"China East\", or \"North Europe\"") |
1628 | - flag.StringVar(&key, "key", "", "A valid storage account key (base64 encoded), defaults to the empty string (i.e. anonymous access)") |
1629 | - flag.StringVar(&container, "container", "", "Name of the container to use") |
1630 | - flag.StringVar(&filename, "filename", "", "File containing blob/page to upload/download") |
1631 | - flag.StringVar(&prefix, "prefix", "", "Prefix to match when listing blobs") |
1632 | - flag.StringVar(&blobname, "blobname", "", "Name of blob in container") |
1633 | - flag.StringVar(&blobtype, "blobtype", "", "Type of blob, 'page' or 'block'") |
1634 | - flag.IntVar(&size, "size", 0, "Size of blob to create for a page 'putblob'") |
1635 | - 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") |
1636 | - flag.StringVar(&acl, "acl", "", "When using 'containeracl', specify an ACL type") |
1637 | - flag.Parse() |
1638 | - |
1639 | - if help { |
1640 | - return "", nil |
1641 | - } |
1642 | - |
1643 | - opName := flag.Arg(0) |
1644 | - if opName == "" { |
1645 | - return "", fmt.Errorf("No operation specified") |
1646 | - } |
1647 | - |
1648 | - requiredArgs := []string{"account", "location"} |
1649 | - for _, arg := range requiredArgs { |
1650 | - if !argumentGiven(arg) { |
1651 | - return "", fmt.Errorf("Must supply %q parameter.", arg) |
1652 | - } |
1653 | - } |
1654 | - |
1655 | - if len(flag.Args()) != 1 { |
1656 | - return "", badOperationError() |
1657 | - } |
1658 | - |
1659 | - op, isDefined := operationsByName[opName] |
1660 | - if !isDefined { |
1661 | - return "", badOperationError() |
1662 | - } |
1663 | - |
1664 | - for _, arg := range op.requiredArgs { |
1665 | - if !argumentGiven(arg) { |
1666 | - return "", fmt.Errorf("%q requires these options: %v", op.name, op.requiredArgs) |
1667 | - } |
1668 | - } |
1669 | - |
1670 | - if op.validate != nil { |
1671 | - err := op.validate() |
1672 | - if err != nil { |
1673 | - return "", err |
1674 | - } |
1675 | - } |
1676 | - |
1677 | - return op.name, nil |
1678 | + flag.BoolVar(&help, "h", false, "Show usage and exit") |
1679 | + |
1680 | + flag.StringVar(&account, "account", "", "Storage account name") |
1681 | + flag.StringVar(&location, "location", "", "Azure location, e.g. \"West US\", \"China East\", or \"North Europe\"") |
1682 | + flag.StringVar(&key, "key", "", "A valid storage account key (base64 encoded), defaults to the empty string (i.e. anonymous access)") |
1683 | + flag.StringVar(&container, "container", "", "Name of the container to use") |
1684 | + flag.StringVar(&filename, "filename", "", "File containing blob/page to upload/download") |
1685 | + flag.StringVar(&prefix, "prefix", "", "Prefix to match when listing blobs") |
1686 | + flag.StringVar(&blobname, "blobname", "", "Name of blob in container") |
1687 | + flag.StringVar(&blobtype, "blobtype", "", "Type of blob, 'page' or 'block'") |
1688 | + flag.IntVar(&size, "size", 0, "Size of blob to create for a page 'putblob'") |
1689 | + 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 | + flag.StringVar(&acl, "acl", "", "When using 'containeracl', specify an ACL type") |
1691 | + flag.Parse() |
1692 | + |
1693 | + if help { |
1694 | + return "", nil |
1695 | + } |
1696 | + |
1697 | + opName := flag.Arg(0) |
1698 | + if opName == "" { |
1699 | + return "", fmt.Errorf("No operation specified") |
1700 | + } |
1701 | + |
1702 | + requiredArgs := []string{"account", "location"} |
1703 | + for _, arg := range requiredArgs { |
1704 | + if !argumentGiven(arg) { |
1705 | + return "", fmt.Errorf("Must supply %q parameter.", arg) |
1706 | + } |
1707 | + } |
1708 | + |
1709 | + if len(flag.Args()) != 1 { |
1710 | + return "", badOperationError() |
1711 | + } |
1712 | + |
1713 | + op, isDefined := operationsByName[opName] |
1714 | + if !isDefined { |
1715 | + return "", badOperationError() |
1716 | + } |
1717 | + |
1718 | + for _, arg := range op.requiredArgs { |
1719 | + if !argumentGiven(arg) { |
1720 | + return "", fmt.Errorf("%q requires these options: %v", op.name, op.requiredArgs) |
1721 | + } |
1722 | + } |
1723 | + |
1724 | + if op.validate != nil { |
1725 | + err := op.validate() |
1726 | + if err != nil { |
1727 | + return "", err |
1728 | + } |
1729 | + } |
1730 | + |
1731 | + return op.name, nil |
1732 | } |
1733 | |
1734 | func Usage() { |
1735 | - fmt.Fprintf( |
1736 | - os.Stderr, |
1737 | - "Usage:\n %s [args] <%s>\n", |
1738 | - os.Args[0], |
1739 | - strings.Join(operationNames, "|")) |
1740 | - flag.PrintDefaults() |
1741 | + fmt.Fprintf( |
1742 | + os.Stderr, |
1743 | + "Usage:\n %s [args] <%s>\n", |
1744 | + os.Args[0], |
1745 | + strings.Join(operationNames, "|")) |
1746 | + flag.PrintDefaults() |
1747 | |
1748 | - fmt.Fprintf(os.Stderr, ` |
1749 | + fmt.Fprintf(os.Stderr, ` |
1750 | This is an example of how to interact with the Azure storage service. |
1751 | It is not a complete example but it does give a useful way to do do some |
1752 | basic operations. |
1753 | @@ -255,142 +255,142 @@ |
1754 | invocation parameters: |
1755 | `) |
1756 | |
1757 | - for _, op := range operations { |
1758 | - fmt.Fprintf(os.Stderr, "\n %s:\n %s\n", op.description, op.example) |
1759 | - } |
1760 | + for _, op := range operations { |
1761 | + fmt.Fprintf(os.Stderr, "\n %s:\n %s\n", op.description, op.example) |
1762 | + } |
1763 | } |
1764 | |
1765 | func dumpError(err error) { |
1766 | - if err != nil { |
1767 | - fmt.Fprintf(os.Stderr, "ERROR:") |
1768 | - fmt.Fprintf(os.Stderr, "%s\n", err) |
1769 | - } |
1770 | + if err != nil { |
1771 | + fmt.Fprintf(os.Stderr, "ERROR:") |
1772 | + fmt.Fprintf(os.Stderr, "%s\n", err) |
1773 | + } |
1774 | } |
1775 | |
1776 | func listcontainers(storage gwacl.StorageContext) { |
1777 | - res, e := storage.ListAllContainers() |
1778 | - if e != nil { |
1779 | - dumpError(e) |
1780 | - return |
1781 | - } |
1782 | - for _, c := range res.Containers { |
1783 | - // TODO: embellish with the other container data |
1784 | - fmt.Println(c.Name) |
1785 | - } |
1786 | + res, e := storage.ListAllContainers() |
1787 | + if e != nil { |
1788 | + dumpError(e) |
1789 | + return |
1790 | + } |
1791 | + for _, c := range res.Containers { |
1792 | + // TODO: embellish with the other container data |
1793 | + fmt.Println(c.Name) |
1794 | + } |
1795 | } |
1796 | |
1797 | func containeracl(storage gwacl.StorageContext) { |
1798 | - err := storage.SetContainerACL(&gwacl.SetContainerACLRequest{ |
1799 | - Container: container, |
1800 | - Access: acl, |
1801 | - }) |
1802 | - dumpError(err) |
1803 | + err := storage.SetContainerACL(&gwacl.SetContainerACLRequest{ |
1804 | + Container: container, |
1805 | + Access: acl, |
1806 | + }) |
1807 | + dumpError(err) |
1808 | } |
1809 | |
1810 | func list(storage gwacl.StorageContext) { |
1811 | - request := &gwacl.ListBlobsRequest{ |
1812 | - Container: container, Prefix: prefix} |
1813 | - res, err := storage.ListAllBlobs(request) |
1814 | - if err != nil { |
1815 | - dumpError(err) |
1816 | - return |
1817 | - } |
1818 | - for _, b := range res.Blobs { |
1819 | - fmt.Printf("%s, %s, %s\n", b.ContentLength, b.LastModified, b.Name) |
1820 | - } |
1821 | + request := &gwacl.ListBlobsRequest{ |
1822 | + Container: container, Prefix: prefix} |
1823 | + res, err := storage.ListAllBlobs(request) |
1824 | + if err != nil { |
1825 | + dumpError(err) |
1826 | + return |
1827 | + } |
1828 | + for _, b := range res.Blobs { |
1829 | + fmt.Printf("%s, %s, %s\n", b.ContentLength, b.LastModified, b.Name) |
1830 | + } |
1831 | } |
1832 | |
1833 | func addblock(storage gwacl.StorageContext) { |
1834 | - var err error |
1835 | - file, err := os.Open(filename) |
1836 | - if err != nil { |
1837 | - dumpError(err) |
1838 | - return |
1839 | - } |
1840 | - defer file.Close() |
1841 | + var err error |
1842 | + file, err := os.Open(filename) |
1843 | + if err != nil { |
1844 | + dumpError(err) |
1845 | + return |
1846 | + } |
1847 | + defer file.Close() |
1848 | |
1849 | - err = storage.UploadBlockBlob(container, filename, file) |
1850 | - if err != nil { |
1851 | - dumpError(err) |
1852 | - return |
1853 | - } |
1854 | + err = storage.UploadBlockBlob(container, filename, file) |
1855 | + if err != nil { |
1856 | + dumpError(err) |
1857 | + return |
1858 | + } |
1859 | } |
1860 | |
1861 | func deleteblob(storage gwacl.StorageContext) { |
1862 | - err := storage.DeleteBlob(container, filename) |
1863 | - dumpError(err) |
1864 | + err := storage.DeleteBlob(container, filename) |
1865 | + dumpError(err) |
1866 | } |
1867 | |
1868 | func getblob(storage gwacl.StorageContext) { |
1869 | - var err error |
1870 | - file, err := storage.GetBlob(container, filename) |
1871 | - if err != nil { |
1872 | - dumpError(err) |
1873 | - return |
1874 | - } |
1875 | - data, err := ioutil.ReadAll(file) |
1876 | - if err != nil { |
1877 | - dumpError(err) |
1878 | - return |
1879 | - } |
1880 | - os.Stdout.Write(data) |
1881 | + var err error |
1882 | + file, err := storage.GetBlob(container, filename) |
1883 | + if err != nil { |
1884 | + dumpError(err) |
1885 | + return |
1886 | + } |
1887 | + data, err := ioutil.ReadAll(file) |
1888 | + if err != nil { |
1889 | + dumpError(err) |
1890 | + return |
1891 | + } |
1892 | + os.Stdout.Write(data) |
1893 | } |
1894 | |
1895 | func putblob(storage gwacl.StorageContext) { |
1896 | - err := storage.PutBlob(&gwacl.PutBlobRequest{ |
1897 | - Container: container, |
1898 | - BlobType: blobtype, |
1899 | - Filename: blobname, |
1900 | - Size: size, |
1901 | - }) |
1902 | - dumpError(err) |
1903 | + err := storage.PutBlob(&gwacl.PutBlobRequest{ |
1904 | + Container: container, |
1905 | + BlobType: blobtype, |
1906 | + Filename: blobname, |
1907 | + Size: size, |
1908 | + }) |
1909 | + dumpError(err) |
1910 | } |
1911 | |
1912 | func putpage(storage gwacl.StorageContext) { |
1913 | - var err error |
1914 | - file, err := os.Open(filename) |
1915 | - if err != nil { |
1916 | - dumpError(err) |
1917 | - return |
1918 | - } |
1919 | - defer file.Close() |
1920 | - |
1921 | - var start, end int |
1922 | - fmt.Sscanf(pagerange, "%d-%d", &start, &end) |
1923 | - |
1924 | - err = storage.PutPage(&gwacl.PutPageRequest{ |
1925 | - Container: container, |
1926 | - Filename: blobname, |
1927 | - StartRange: start, |
1928 | - EndRange: end, |
1929 | - Data: file, |
1930 | - }) |
1931 | - if err != nil { |
1932 | - dumpError(err) |
1933 | - return |
1934 | - } |
1935 | + var err error |
1936 | + file, err := os.Open(filename) |
1937 | + if err != nil { |
1938 | + dumpError(err) |
1939 | + return |
1940 | + } |
1941 | + defer file.Close() |
1942 | + |
1943 | + var start, end int |
1944 | + fmt.Sscanf(pagerange, "%d-%d", &start, &end) |
1945 | + |
1946 | + err = storage.PutPage(&gwacl.PutPageRequest{ |
1947 | + Container: container, |
1948 | + Filename: blobname, |
1949 | + StartRange: start, |
1950 | + EndRange: end, |
1951 | + Data: file, |
1952 | + }) |
1953 | + if err != nil { |
1954 | + dumpError(err) |
1955 | + return |
1956 | + } |
1957 | } |
1958 | |
1959 | func main() { |
1960 | - flag.Usage = Usage |
1961 | - var err error |
1962 | - op, err := getParams() |
1963 | - if err != nil { |
1964 | - fmt.Fprintf(os.Stderr, "%s\n", err.Error()) |
1965 | - fmt.Fprintf(os.Stderr, "Use -h for help with using this program.\n") |
1966 | - os.Exit(1) |
1967 | - } |
1968 | - if help { |
1969 | - Usage() |
1970 | - os.Exit(0) |
1971 | - } |
1972 | - |
1973 | - storage := gwacl.StorageContext{ |
1974 | - Account: account, |
1975 | - Key: key, |
1976 | - AzureEndpoint: gwacl.GetEndpoint(location), |
1977 | - } |
1978 | - |
1979 | - perform := operationsByName[op].implementation |
1980 | - perform(storage) |
1981 | + flag.Usage = Usage |
1982 | + var err error |
1983 | + op, err := getParams() |
1984 | + if err != nil { |
1985 | + fmt.Fprintf(os.Stderr, "%s\n", err.Error()) |
1986 | + fmt.Fprintf(os.Stderr, "Use -h for help with using this program.\n") |
1987 | + os.Exit(1) |
1988 | + } |
1989 | + if help { |
1990 | + Usage() |
1991 | + os.Exit(0) |
1992 | + } |
1993 | + |
1994 | + storage := gwacl.StorageContext{ |
1995 | + Account: account, |
1996 | + Key: key, |
1997 | + AzureEndpoint: gwacl.GetEndpoint(location), |
1998 | + } |
1999 | + |
2000 | + perform := operationsByName[op].implementation |
2001 | + perform(storage) |
2002 | } |
2003 | |
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 | package http |
2009 | |
2010 | import ( |
2011 | - "bufio" |
2012 | - "bytes" |
2013 | - "errors" |
2014 | - "io" |
2015 | - "strconv" |
2016 | + "bufio" |
2017 | + "bytes" |
2018 | + "errors" |
2019 | + "io" |
2020 | + "strconv" |
2021 | ) |
2022 | |
2023 | const maxLineLength = 4096 // assumed <= bufio.defaultBufSize |
2024 | @@ -28,60 +28,60 @@ |
2025 | // newChunkedReader is not needed by normal applications. The http package |
2026 | // automatically decodes chunking when reading response bodies. |
2027 | func newChunkedReader(r io.Reader) io.Reader { |
2028 | - br, ok := r.(*bufio.Reader) |
2029 | - if !ok { |
2030 | - br = bufio.NewReader(r) |
2031 | - } |
2032 | - return &chunkedReader{r: br} |
2033 | + br, ok := r.(*bufio.Reader) |
2034 | + if !ok { |
2035 | + br = bufio.NewReader(r) |
2036 | + } |
2037 | + return &chunkedReader{r: br} |
2038 | } |
2039 | |
2040 | type chunkedReader struct { |
2041 | - r *bufio.Reader |
2042 | - n uint64 // unread bytes in chunk |
2043 | - err error |
2044 | + r *bufio.Reader |
2045 | + n uint64 // unread bytes in chunk |
2046 | + err error |
2047 | } |
2048 | |
2049 | func (cr *chunkedReader) beginChunk() { |
2050 | - // chunk-size CRLF |
2051 | - var line string |
2052 | - line, cr.err = readLine(cr.r) |
2053 | - if cr.err != nil { |
2054 | - return |
2055 | - } |
2056 | - cr.n, cr.err = strconv.ParseUint(line, 16, 64) |
2057 | - if cr.err != nil { |
2058 | - return |
2059 | - } |
2060 | - if cr.n == 0 { |
2061 | - cr.err = io.EOF |
2062 | - } |
2063 | + // chunk-size CRLF |
2064 | + var line string |
2065 | + line, cr.err = readLine(cr.r) |
2066 | + if cr.err != nil { |
2067 | + return |
2068 | + } |
2069 | + cr.n, cr.err = strconv.ParseUint(line, 16, 64) |
2070 | + if cr.err != nil { |
2071 | + return |
2072 | + } |
2073 | + if cr.n == 0 { |
2074 | + cr.err = io.EOF |
2075 | + } |
2076 | } |
2077 | |
2078 | func (cr *chunkedReader) Read(b []uint8) (n int, err error) { |
2079 | - if cr.err != nil { |
2080 | - return 0, cr.err |
2081 | - } |
2082 | - if cr.n == 0 { |
2083 | - cr.beginChunk() |
2084 | - if cr.err != nil { |
2085 | - return 0, cr.err |
2086 | - } |
2087 | - } |
2088 | - if uint64(len(b)) > cr.n { |
2089 | - b = b[0:cr.n] |
2090 | - } |
2091 | - n, cr.err = cr.r.Read(b) |
2092 | - cr.n -= uint64(n) |
2093 | - if cr.n == 0 && cr.err == nil { |
2094 | - // end of chunk (CRLF) |
2095 | - b := make([]byte, 2) |
2096 | - if _, cr.err = io.ReadFull(cr.r, b); cr.err == nil { |
2097 | - if b[0] != '\r' || b[1] != '\n' { |
2098 | - cr.err = errors.New("malformed chunked encoding") |
2099 | - } |
2100 | - } |
2101 | - } |
2102 | - return n, cr.err |
2103 | + if cr.err != nil { |
2104 | + return 0, cr.err |
2105 | + } |
2106 | + if cr.n == 0 { |
2107 | + cr.beginChunk() |
2108 | + if cr.err != nil { |
2109 | + return 0, cr.err |
2110 | + } |
2111 | + } |
2112 | + if uint64(len(b)) > cr.n { |
2113 | + b = b[0:cr.n] |
2114 | + } |
2115 | + n, cr.err = cr.r.Read(b) |
2116 | + cr.n -= uint64(n) |
2117 | + if cr.n == 0 && cr.err == nil { |
2118 | + // end of chunk (CRLF) |
2119 | + b := make([]byte, 2) |
2120 | + if _, cr.err = io.ReadFull(cr.r, b); cr.err == nil { |
2121 | + if b[0] != '\r' || b[1] != '\n' { |
2122 | + cr.err = errors.New("malformed chunked encoding") |
2123 | + } |
2124 | + } |
2125 | + } |
2126 | + return n, cr.err |
2127 | } |
2128 | |
2129 | // Read a line of bytes (up to \n) from b. |
2130 | @@ -89,33 +89,33 @@ |
2131 | // The returned bytes are a pointer into storage in |
2132 | // the bufio, so they are only valid until the next bufio read. |
2133 | func readLineBytes(b *bufio.Reader) (p []byte, err error) { |
2134 | - if p, err = b.ReadSlice('\n'); err != nil { |
2135 | - // We always know when EOF is coming. |
2136 | - // If the caller asked for a line, there should be a line. |
2137 | - if err == io.EOF { |
2138 | - err = io.ErrUnexpectedEOF |
2139 | - } else if err == bufio.ErrBufferFull { |
2140 | - err = ErrLineTooLong |
2141 | - } |
2142 | - return nil, err |
2143 | - } |
2144 | - if len(p) >= maxLineLength { |
2145 | - return nil, ErrLineTooLong |
2146 | - } |
2147 | - |
2148 | - // Chop off trailing white space. |
2149 | - p = bytes.TrimRight(p, " \r\t\n") |
2150 | - |
2151 | - return p, nil |
2152 | + if p, err = b.ReadSlice('\n'); err != nil { |
2153 | + // We always know when EOF is coming. |
2154 | + // If the caller asked for a line, there should be a line. |
2155 | + if err == io.EOF { |
2156 | + err = io.ErrUnexpectedEOF |
2157 | + } else if err == bufio.ErrBufferFull { |
2158 | + err = ErrLineTooLong |
2159 | + } |
2160 | + return nil, err |
2161 | + } |
2162 | + if len(p) >= maxLineLength { |
2163 | + return nil, ErrLineTooLong |
2164 | + } |
2165 | + |
2166 | + // Chop off trailing white space. |
2167 | + p = bytes.TrimRight(p, " \r\t\n") |
2168 | + |
2169 | + return p, nil |
2170 | } |
2171 | |
2172 | // readLineBytes, but convert the bytes into a string. |
2173 | func readLine(b *bufio.Reader) (s string, err error) { |
2174 | - p, e := readLineBytes(b) |
2175 | - if e != nil { |
2176 | - return "", e |
2177 | - } |
2178 | - return string(p), nil |
2179 | + p, e := readLineBytes(b) |
2180 | + if e != nil { |
2181 | + return "", e |
2182 | + } |
2183 | + return string(p), nil |
2184 | } |
2185 | |
2186 | // newChunkedWriter returns a new chunkedWriter that translates writes into HTTP |
2187 | @@ -128,13 +128,13 @@ |
2188 | // would result in double chunking or chunking with a Content-Length |
2189 | // length, both of which are wrong. |
2190 | func newChunkedWriter(w io.Writer) io.WriteCloser { |
2191 | - return &chunkedWriter{w} |
2192 | + return &chunkedWriter{w} |
2193 | } |
2194 | |
2195 | // Writing to chunkedWriter translates to writing in HTTP chunked Transfer |
2196 | // Encoding wire format to the underlying Wire chunkedWriter. |
2197 | type chunkedWriter struct { |
2198 | - Wire io.Writer |
2199 | + Wire io.Writer |
2200 | } |
2201 | |
2202 | // Write the contents of data as one chunk to Wire. |
2203 | @@ -142,29 +142,29 @@ |
2204 | // a bug since it does not check for success of io.WriteString |
2205 | func (cw *chunkedWriter) Write(data []byte) (n int, err error) { |
2206 | |
2207 | - // Don't send 0-length data. It looks like EOF for chunked encoding. |
2208 | - if len(data) == 0 { |
2209 | - return 0, nil |
2210 | - } |
2211 | - |
2212 | - head := strconv.FormatInt(int64(len(data)), 16) + "\r\n" |
2213 | - |
2214 | - if _, err = io.WriteString(cw.Wire, head); err != nil { |
2215 | - return 0, err |
2216 | - } |
2217 | - if n, err = cw.Wire.Write(data); err != nil { |
2218 | - return |
2219 | - } |
2220 | - if n != len(data) { |
2221 | - err = io.ErrShortWrite |
2222 | - return |
2223 | - } |
2224 | - _, err = io.WriteString(cw.Wire, "\r\n") |
2225 | - |
2226 | - return |
2227 | + // Don't send 0-length data. It looks like EOF for chunked encoding. |
2228 | + if len(data) == 0 { |
2229 | + return 0, nil |
2230 | + } |
2231 | + |
2232 | + head := strconv.FormatInt(int64(len(data)), 16) + "\r\n" |
2233 | + |
2234 | + if _, err = io.WriteString(cw.Wire, head); err != nil { |
2235 | + return 0, err |
2236 | + } |
2237 | + if n, err = cw.Wire.Write(data); err != nil { |
2238 | + return |
2239 | + } |
2240 | + if n != len(data) { |
2241 | + err = io.ErrShortWrite |
2242 | + return |
2243 | + } |
2244 | + _, err = io.WriteString(cw.Wire, "\r\n") |
2245 | + |
2246 | + return |
2247 | } |
2248 | |
2249 | func (cw *chunkedWriter) Close() error { |
2250 | - _, err := io.WriteString(cw.Wire, "0\r\n") |
2251 | - return err |
2252 | + _, err := io.WriteString(cw.Wire, "0\r\n") |
2253 | + return err |
2254 | } |
2255 | |
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 | package http |
2261 | |
2262 | import ( |
2263 | - "encoding/base64" |
2264 | - "errors" |
2265 | - "fmt" |
2266 | - "io" |
2267 | - "net/url" |
2268 | - "strings" |
2269 | + "encoding/base64" |
2270 | + "errors" |
2271 | + "fmt" |
2272 | + "io" |
2273 | + "net/url" |
2274 | + "strings" |
2275 | ) |
2276 | |
2277 | // A Client is an HTTP client. Its zero value (DefaultClient) is a usable client |
2278 | @@ -25,26 +25,26 @@ |
2279 | // TCP connections), so Clients should be reused instead of created as |
2280 | // needed. Clients are safe for concurrent use by multiple goroutines. |
2281 | type Client struct { |
2282 | - // Transport specifies the mechanism by which individual |
2283 | - // HTTP requests are made. |
2284 | - // If nil, DefaultTransport is used. |
2285 | - Transport RoundTripper |
2286 | - |
2287 | - // CheckRedirect specifies the policy for handling redirects. |
2288 | - // If CheckRedirect is not nil, the client calls it before |
2289 | - // following an HTTP redirect. The arguments req and via |
2290 | - // are the upcoming request and the requests made already, |
2291 | - // oldest first. If CheckRedirect returns an error, the client |
2292 | - // returns that error instead of issue the Request req. |
2293 | - // |
2294 | - // If CheckRedirect is nil, the Client uses its default policy, |
2295 | - // which is to stop after 10 consecutive requests. |
2296 | - CheckRedirect func(req *Request, via []*Request) error |
2297 | - |
2298 | - // Jar specifies the cookie jar. |
2299 | - // If Jar is nil, cookies are not sent in requests and ignored |
2300 | - // in responses. |
2301 | - Jar CookieJar |
2302 | + // Transport specifies the mechanism by which individual |
2303 | + // HTTP requests are made. |
2304 | + // If nil, DefaultTransport is used. |
2305 | + Transport RoundTripper |
2306 | + |
2307 | + // CheckRedirect specifies the policy for handling redirects. |
2308 | + // If CheckRedirect is not nil, the client calls it before |
2309 | + // following an HTTP redirect. The arguments req and via |
2310 | + // are the upcoming request and the requests made already, |
2311 | + // oldest first. If CheckRedirect returns an error, the client |
2312 | + // returns that error instead of issue the Request req. |
2313 | + // |
2314 | + // If CheckRedirect is nil, the Client uses its default policy, |
2315 | + // which is to stop after 10 consecutive requests. |
2316 | + CheckRedirect func(req *Request, via []*Request) error |
2317 | + |
2318 | + // Jar specifies the cookie jar. |
2319 | + // If Jar is nil, cookies are not sent in requests and ignored |
2320 | + // in responses. |
2321 | + Jar CookieJar |
2322 | } |
2323 | |
2324 | // DefaultClient is the default Client and is used by Get, Head, and Post. |
2325 | @@ -56,20 +56,20 @@ |
2326 | // A RoundTripper must be safe for concurrent use by multiple |
2327 | // goroutines. |
2328 | type RoundTripper interface { |
2329 | - // RoundTrip executes a single HTTP transaction, returning |
2330 | - // the Response for the request req. RoundTrip should not |
2331 | - // attempt to interpret the response. In particular, |
2332 | - // RoundTrip must return err == nil if it obtained a response, |
2333 | - // regardless of the response's HTTP status code. A non-nil |
2334 | - // err should be reserved for failure to obtain a response. |
2335 | - // Similarly, RoundTrip should not attempt to handle |
2336 | - // higher-level protocol details such as redirects, |
2337 | - // authentication, or cookies. |
2338 | - // |
2339 | - // RoundTrip should not modify the request, except for |
2340 | - // consuming the Body. The request's URL and Header fields |
2341 | - // are guaranteed to be initialized. |
2342 | - RoundTrip(*Request) (*Response, error) |
2343 | + // RoundTrip executes a single HTTP transaction, returning |
2344 | + // the Response for the request req. RoundTrip should not |
2345 | + // attempt to interpret the response. In particular, |
2346 | + // RoundTrip must return err == nil if it obtained a response, |
2347 | + // regardless of the response's HTTP status code. A non-nil |
2348 | + // err should be reserved for failure to obtain a response. |
2349 | + // Similarly, RoundTrip should not attempt to handle |
2350 | + // higher-level protocol details such as redirects, |
2351 | + // authentication, or cookies. |
2352 | + // |
2353 | + // RoundTrip should not modify the request, except for |
2354 | + // consuming the Body. The request's URL and Header fields |
2355 | + // are guaranteed to be initialized. |
2356 | + RoundTrip(*Request) (*Response, error) |
2357 | } |
2358 | |
2359 | // Given a string of the form "host", "host:port", or "[ipv6::address]:port", |
2360 | @@ -80,8 +80,8 @@ |
2361 | // bufio.Reader through which we read the response, and the underlying |
2362 | // network connection. |
2363 | type readClose struct { |
2364 | - io.Reader |
2365 | - io.Closer |
2366 | + io.Reader |
2367 | + io.Closer |
2368 | } |
2369 | |
2370 | // Do sends an HTTP request and returns an HTTP response, following |
2371 | @@ -96,51 +96,51 @@ |
2372 | // |
2373 | // Generally Get, Post, or PostForm will be used instead of Do. |
2374 | func (c *Client) Do(req *Request) (resp *Response, err error) { |
2375 | - if req.Method == "GET" || req.Method == "HEAD" { |
2376 | - return c.doFollowingRedirects(req) |
2377 | - } |
2378 | - return send(req, c.Transport) |
2379 | + if req.Method == "GET" || req.Method == "HEAD" { |
2380 | + return c.doFollowingRedirects(req) |
2381 | + } |
2382 | + return send(req, c.Transport) |
2383 | } |
2384 | |
2385 | // send issues an HTTP request. Caller should close resp.Body when done reading from it. |
2386 | func send(req *Request, t RoundTripper) (resp *Response, err error) { |
2387 | - if t == nil { |
2388 | - t = DefaultTransport |
2389 | - if t == nil { |
2390 | - err = errors.New("http: no Client.Transport or DefaultTransport") |
2391 | - return |
2392 | - } |
2393 | - } |
2394 | - |
2395 | - if req.URL == nil { |
2396 | - return nil, errors.New("http: nil Request.URL") |
2397 | - } |
2398 | - |
2399 | - if req.RequestURI != "" { |
2400 | - return nil, errors.New("http: Request.RequestURI can't be set in client requests.") |
2401 | - } |
2402 | - |
2403 | - // Most the callers of send (Get, Post, et al) don't need |
2404 | - // Headers, leaving it uninitialized. We guarantee to the |
2405 | - // Transport that this has been initialized, though. |
2406 | - if req.Header == nil { |
2407 | - req.Header = make(Header) |
2408 | - } |
2409 | - |
2410 | - if u := req.URL.User; u != nil { |
2411 | - req.Header.Set("Authorization", "Basic "+base64.URLEncoding.EncodeToString([]byte(u.String()))) |
2412 | - } |
2413 | - return t.RoundTrip(req) |
2414 | + if t == nil { |
2415 | + t = DefaultTransport |
2416 | + if t == nil { |
2417 | + err = errors.New("http: no Client.Transport or DefaultTransport") |
2418 | + return |
2419 | + } |
2420 | + } |
2421 | + |
2422 | + if req.URL == nil { |
2423 | + return nil, errors.New("http: nil Request.URL") |
2424 | + } |
2425 | + |
2426 | + if req.RequestURI != "" { |
2427 | + return nil, errors.New("http: Request.RequestURI can't be set in client requests.") |
2428 | + } |
2429 | + |
2430 | + // Most the callers of send (Get, Post, et al) don't need |
2431 | + // Headers, leaving it uninitialized. We guarantee to the |
2432 | + // Transport that this has been initialized, though. |
2433 | + if req.Header == nil { |
2434 | + req.Header = make(Header) |
2435 | + } |
2436 | + |
2437 | + if u := req.URL.User; u != nil { |
2438 | + req.Header.Set("Authorization", "Basic "+base64.URLEncoding.EncodeToString([]byte(u.String()))) |
2439 | + } |
2440 | + return t.RoundTrip(req) |
2441 | } |
2442 | |
2443 | // True if the specified HTTP status code is one for which the Get utility should |
2444 | // automatically redirect. |
2445 | func shouldRedirect(statusCode int) bool { |
2446 | - switch statusCode { |
2447 | - case StatusMovedPermanently, StatusFound, StatusSeeOther, StatusTemporaryRedirect: |
2448 | - return true |
2449 | - } |
2450 | - return false |
2451 | + switch statusCode { |
2452 | + case StatusMovedPermanently, StatusFound, StatusSeeOther, StatusTemporaryRedirect: |
2453 | + return true |
2454 | + } |
2455 | + return false |
2456 | } |
2457 | |
2458 | // Get issues a GET to the specified URL. If the response is one of the following |
2459 | @@ -155,7 +155,7 @@ |
2460 | // |
2461 | // Get is a wrapper around DefaultClient.Get. |
2462 | func Get(url string) (r *Response, err error) { |
2463 | - return DefaultClient.Get(url) |
2464 | + return DefaultClient.Get(url) |
2465 | } |
2466 | |
2467 | // Get issues a GET to the specified URL. If the response is one of the |
2468 | @@ -169,95 +169,95 @@ |
2469 | // |
2470 | // Caller should close r.Body when done reading from it. |
2471 | func (c *Client) Get(url string) (r *Response, err error) { |
2472 | - req, err := NewRequest("GET", url, nil) |
2473 | - if err != nil { |
2474 | - return nil, err |
2475 | - } |
2476 | - return c.doFollowingRedirects(req) |
2477 | + req, err := NewRequest("GET", url, nil) |
2478 | + if err != nil { |
2479 | + return nil, err |
2480 | + } |
2481 | + return c.doFollowingRedirects(req) |
2482 | } |
2483 | |
2484 | func (c *Client) doFollowingRedirects(ireq *Request) (r *Response, err error) { |
2485 | - // TODO: if/when we add cookie support, the redirected request shouldn't |
2486 | - // necessarily supply the same cookies as the original. |
2487 | - var base *url.URL |
2488 | - redirectChecker := c.CheckRedirect |
2489 | - if redirectChecker == nil { |
2490 | - redirectChecker = defaultCheckRedirect |
2491 | - } |
2492 | - var via []*Request |
2493 | - |
2494 | - if ireq.URL == nil { |
2495 | - return nil, errors.New("http: nil Request.URL") |
2496 | - } |
2497 | - |
2498 | - jar := c.Jar |
2499 | - if jar == nil { |
2500 | - jar = blackHoleJar{} |
2501 | - } |
2502 | - |
2503 | - req := ireq |
2504 | - urlStr := "" // next relative or absolute URL to fetch (after first request) |
2505 | - for redirect := 0; ; redirect++ { |
2506 | - if redirect != 0 { |
2507 | - req = new(Request) |
2508 | - req.Method = ireq.Method |
2509 | - req.Header = make(Header) |
2510 | - req.URL, err = base.Parse(urlStr) |
2511 | - if err != nil { |
2512 | - break |
2513 | - } |
2514 | - if len(via) > 0 { |
2515 | - // Add the Referer header. |
2516 | - lastReq := via[len(via)-1] |
2517 | - if lastReq.URL.Scheme != "https" { |
2518 | - req.Header.Set("Referer", lastReq.URL.String()) |
2519 | - } |
2520 | - |
2521 | - err = redirectChecker(req, via) |
2522 | - if err != nil { |
2523 | - break |
2524 | - } |
2525 | - } |
2526 | - } |
2527 | - |
2528 | - for _, cookie := range jar.Cookies(req.URL) { |
2529 | - req.AddCookie(cookie) |
2530 | - } |
2531 | - urlStr = req.URL.String() |
2532 | - if r, err = send(req, c.Transport); err != nil { |
2533 | - break |
2534 | - } |
2535 | - if c := r.Cookies(); len(c) > 0 { |
2536 | - jar.SetCookies(req.URL, c) |
2537 | - } |
2538 | - |
2539 | - if shouldRedirect(r.StatusCode) { |
2540 | - r.Body.Close() |
2541 | - if urlStr = r.Header.Get("Location"); urlStr == "" { |
2542 | - err = errors.New(fmt.Sprintf("%d response missing Location header", r.StatusCode)) |
2543 | - break |
2544 | - } |
2545 | - base = req.URL |
2546 | - via = append(via, req) |
2547 | - continue |
2548 | - } |
2549 | - return |
2550 | - } |
2551 | - |
2552 | - method := ireq.Method |
2553 | - err = &url.Error{ |
2554 | - Op: method[0:1] + strings.ToLower(method[1:]), |
2555 | - URL: urlStr, |
2556 | - Err: err, |
2557 | - } |
2558 | - return |
2559 | + // TODO: if/when we add cookie support, the redirected request shouldn't |
2560 | + // necessarily supply the same cookies as the original. |
2561 | + var base *url.URL |
2562 | + redirectChecker := c.CheckRedirect |
2563 | + if redirectChecker == nil { |
2564 | + redirectChecker = defaultCheckRedirect |
2565 | + } |
2566 | + var via []*Request |
2567 | + |
2568 | + if ireq.URL == nil { |
2569 | + return nil, errors.New("http: nil Request.URL") |
2570 | + } |
2571 | + |
2572 | + jar := c.Jar |
2573 | + if jar == nil { |
2574 | + jar = blackHoleJar{} |
2575 | + } |
2576 | + |
2577 | + req := ireq |
2578 | + urlStr := "" // next relative or absolute URL to fetch (after first request) |
2579 | + for redirect := 0; ; redirect++ { |
2580 | + if redirect != 0 { |
2581 | + req = new(Request) |
2582 | + req.Method = ireq.Method |
2583 | + req.Header = make(Header) |
2584 | + req.URL, err = base.Parse(urlStr) |
2585 | + if err != nil { |
2586 | + break |
2587 | + } |
2588 | + if len(via) > 0 { |
2589 | + // Add the Referer header. |
2590 | + lastReq := via[len(via)-1] |
2591 | + if lastReq.URL.Scheme != "https" { |
2592 | + req.Header.Set("Referer", lastReq.URL.String()) |
2593 | + } |
2594 | + |
2595 | + err = redirectChecker(req, via) |
2596 | + if err != nil { |
2597 | + break |
2598 | + } |
2599 | + } |
2600 | + } |
2601 | + |
2602 | + for _, cookie := range jar.Cookies(req.URL) { |
2603 | + req.AddCookie(cookie) |
2604 | + } |
2605 | + urlStr = req.URL.String() |
2606 | + if r, err = send(req, c.Transport); err != nil { |
2607 | + break |
2608 | + } |
2609 | + if c := r.Cookies(); len(c) > 0 { |
2610 | + jar.SetCookies(req.URL, c) |
2611 | + } |
2612 | + |
2613 | + if shouldRedirect(r.StatusCode) { |
2614 | + r.Body.Close() |
2615 | + if urlStr = r.Header.Get("Location"); urlStr == "" { |
2616 | + err = errors.New(fmt.Sprintf("%d response missing Location header", r.StatusCode)) |
2617 | + break |
2618 | + } |
2619 | + base = req.URL |
2620 | + via = append(via, req) |
2621 | + continue |
2622 | + } |
2623 | + return |
2624 | + } |
2625 | + |
2626 | + method := ireq.Method |
2627 | + err = &url.Error{ |
2628 | + Op: method[0:1] + strings.ToLower(method[1:]), |
2629 | + URL: urlStr, |
2630 | + Err: err, |
2631 | + } |
2632 | + return |
2633 | } |
2634 | |
2635 | func defaultCheckRedirect(req *Request, via []*Request) error { |
2636 | - if len(via) >= 10 { |
2637 | - return errors.New("stopped after 10 redirects") |
2638 | - } |
2639 | - return nil |
2640 | + if len(via) >= 10 { |
2641 | + return errors.New("stopped after 10 redirects") |
2642 | + } |
2643 | + return nil |
2644 | } |
2645 | |
2646 | // Post issues a POST to the specified URL. |
2647 | @@ -266,28 +266,28 @@ |
2648 | // |
2649 | // Post is a wrapper around DefaultClient.Post |
2650 | func Post(url string, bodyType string, body io.Reader) (r *Response, err error) { |
2651 | - return DefaultClient.Post(url, bodyType, body) |
2652 | + return DefaultClient.Post(url, bodyType, body) |
2653 | } |
2654 | |
2655 | // Post issues a POST to the specified URL. |
2656 | // |
2657 | // Caller should close r.Body when done reading from it. |
2658 | func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response, err error) { |
2659 | - req, err := NewRequest("POST", url, body) |
2660 | - if err != nil { |
2661 | - return nil, err |
2662 | - } |
2663 | - req.Header.Set("Content-Type", bodyType) |
2664 | - if c.Jar != nil { |
2665 | - for _, cookie := range c.Jar.Cookies(req.URL) { |
2666 | - req.AddCookie(cookie) |
2667 | - } |
2668 | - } |
2669 | - r, err = send(req, c.Transport) |
2670 | - if err == nil && c.Jar != nil { |
2671 | - c.Jar.SetCookies(req.URL, r.Cookies()) |
2672 | - } |
2673 | - return r, err |
2674 | + req, err := NewRequest("POST", url, body) |
2675 | + if err != nil { |
2676 | + return nil, err |
2677 | + } |
2678 | + req.Header.Set("Content-Type", bodyType) |
2679 | + if c.Jar != nil { |
2680 | + for _, cookie := range c.Jar.Cookies(req.URL) { |
2681 | + req.AddCookie(cookie) |
2682 | + } |
2683 | + } |
2684 | + r, err = send(req, c.Transport) |
2685 | + if err == nil && c.Jar != nil { |
2686 | + c.Jar.SetCookies(req.URL, r.Cookies()) |
2687 | + } |
2688 | + return r, err |
2689 | } |
2690 | |
2691 | // PostForm issues a POST to the specified URL, |
2692 | @@ -297,7 +297,7 @@ |
2693 | // |
2694 | // PostForm is a wrapper around DefaultClient.PostForm |
2695 | func PostForm(url string, data url.Values) (r *Response, err error) { |
2696 | - return DefaultClient.PostForm(url, data) |
2697 | + return DefaultClient.PostForm(url, data) |
2698 | } |
2699 | |
2700 | // PostForm issues a POST to the specified URL, |
2701 | @@ -305,7 +305,7 @@ |
2702 | // |
2703 | // Caller should close r.Body when done reading from it. |
2704 | func (c *Client) PostForm(url string, data url.Values) (r *Response, err error) { |
2705 | - return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) |
2706 | + return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) |
2707 | } |
2708 | |
2709 | // Head issues a HEAD to the specified URL. If the response is one of the |
2710 | @@ -319,7 +319,7 @@ |
2711 | // |
2712 | // Head is a wrapper around DefaultClient.Head |
2713 | func Head(url string) (r *Response, err error) { |
2714 | - return DefaultClient.Head(url) |
2715 | + return DefaultClient.Head(url) |
2716 | } |
2717 | |
2718 | // Head issues a HEAD to the specified URL. If the response is one of the |
2719 | @@ -331,9 +331,9 @@ |
2720 | // 303 (See Other) |
2721 | // 307 (Temporary Redirect) |
2722 | func (c *Client) Head(url string) (r *Response, err error) { |
2723 | - req, err := NewRequest("HEAD", url, nil) |
2724 | - if err != nil { |
2725 | - return nil, err |
2726 | - } |
2727 | - return c.doFollowingRedirects(req) |
2728 | + req, err := NewRequest("HEAD", url, nil) |
2729 | + if err != nil { |
2730 | + return nil, err |
2731 | + } |
2732 | + return c.doFollowingRedirects(req) |
2733 | } |
2734 | |
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 | package http |
2740 | |
2741 | import ( |
2742 | - "bytes" |
2743 | - "fmt" |
2744 | - "strconv" |
2745 | - "strings" |
2746 | - "time" |
2747 | + "bytes" |
2748 | + "fmt" |
2749 | + "strconv" |
2750 | + "strings" |
2751 | + "time" |
2752 | ) |
2753 | |
2754 | // This implementation is done according to RFC 6265: |
2755 | @@ -19,148 +19,148 @@ |
2756 | // A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an |
2757 | // HTTP response or the Cookie header of an HTTP request. |
2758 | type Cookie struct { |
2759 | - Name string |
2760 | - Value string |
2761 | - Path string |
2762 | - Domain string |
2763 | - Expires time.Time |
2764 | - RawExpires string |
2765 | + Name string |
2766 | + Value string |
2767 | + Path string |
2768 | + Domain string |
2769 | + Expires time.Time |
2770 | + RawExpires string |
2771 | |
2772 | - // MaxAge=0 means no 'Max-Age' attribute specified. |
2773 | - // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0' |
2774 | - // MaxAge>0 means Max-Age attribute present and given in seconds |
2775 | - MaxAge int |
2776 | - Secure bool |
2777 | - HttpOnly bool |
2778 | - Raw string |
2779 | - Unparsed []string // Raw text of unparsed attribute-value pairs |
2780 | + // MaxAge=0 means no 'Max-Age' attribute specified. |
2781 | + // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0' |
2782 | + // MaxAge>0 means Max-Age attribute present and given in seconds |
2783 | + MaxAge int |
2784 | + Secure bool |
2785 | + HttpOnly bool |
2786 | + Raw string |
2787 | + Unparsed []string // Raw text of unparsed attribute-value pairs |
2788 | } |
2789 | |
2790 | // readSetCookies parses all "Set-Cookie" values from |
2791 | // the header h and returns the successfully parsed Cookies. |
2792 | func readSetCookies(h Header) []*Cookie { |
2793 | - cookies := []*Cookie{} |
2794 | - for _, line := range h["Set-Cookie"] { |
2795 | - parts := strings.Split(strings.TrimSpace(line), ";") |
2796 | - if len(parts) == 1 && parts[0] == "" { |
2797 | - continue |
2798 | - } |
2799 | - parts[0] = strings.TrimSpace(parts[0]) |
2800 | - j := strings.Index(parts[0], "=") |
2801 | - if j < 0 { |
2802 | - continue |
2803 | - } |
2804 | - name, value := parts[0][:j], parts[0][j+1:] |
2805 | - if !isCookieNameValid(name) { |
2806 | - continue |
2807 | - } |
2808 | - value, success := parseCookieValue(value) |
2809 | - if !success { |
2810 | - continue |
2811 | - } |
2812 | - c := &Cookie{ |
2813 | - Name: name, |
2814 | - Value: value, |
2815 | - Raw: line, |
2816 | - } |
2817 | - for i := 1; i < len(parts); i++ { |
2818 | - parts[i] = strings.TrimSpace(parts[i]) |
2819 | - if len(parts[i]) == 0 { |
2820 | - continue |
2821 | - } |
2822 | + cookies := []*Cookie{} |
2823 | + for _, line := range h["Set-Cookie"] { |
2824 | + parts := strings.Split(strings.TrimSpace(line), ";") |
2825 | + if len(parts) == 1 && parts[0] == "" { |
2826 | + continue |
2827 | + } |
2828 | + parts[0] = strings.TrimSpace(parts[0]) |
2829 | + j := strings.Index(parts[0], "=") |
2830 | + if j < 0 { |
2831 | + continue |
2832 | + } |
2833 | + name, value := parts[0][:j], parts[0][j+1:] |
2834 | + if !isCookieNameValid(name) { |
2835 | + continue |
2836 | + } |
2837 | + value, success := parseCookieValue(value) |
2838 | + if !success { |
2839 | + continue |
2840 | + } |
2841 | + c := &Cookie{ |
2842 | + Name: name, |
2843 | + Value: value, |
2844 | + Raw: line, |
2845 | + } |
2846 | + for i := 1; i < len(parts); i++ { |
2847 | + parts[i] = strings.TrimSpace(parts[i]) |
2848 | + if len(parts[i]) == 0 { |
2849 | + continue |
2850 | + } |
2851 | |
2852 | - attr, val := parts[i], "" |
2853 | - if j := strings.Index(attr, "="); j >= 0 { |
2854 | - attr, val = attr[:j], attr[j+1:] |
2855 | - } |
2856 | - lowerAttr := strings.ToLower(attr) |
2857 | - parseCookieValueFn := parseCookieValue |
2858 | - if lowerAttr == "expires" { |
2859 | - parseCookieValueFn = parseCookieExpiresValue |
2860 | - } |
2861 | - val, success = parseCookieValueFn(val) |
2862 | - if !success { |
2863 | - c.Unparsed = append(c.Unparsed, parts[i]) |
2864 | - continue |
2865 | - } |
2866 | - switch lowerAttr { |
2867 | - case "secure": |
2868 | - c.Secure = true |
2869 | - continue |
2870 | - case "httponly": |
2871 | - c.HttpOnly = true |
2872 | - continue |
2873 | - case "domain": |
2874 | - c.Domain = val |
2875 | - // TODO: Add domain parsing |
2876 | - continue |
2877 | - case "max-age": |
2878 | - secs, err := strconv.Atoi(val) |
2879 | - if err != nil || secs != 0 && val[0] == '0' { |
2880 | - break |
2881 | - } |
2882 | - if secs <= 0 { |
2883 | - c.MaxAge = -1 |
2884 | - } else { |
2885 | - c.MaxAge = secs |
2886 | - } |
2887 | - continue |
2888 | - case "expires": |
2889 | - c.RawExpires = val |
2890 | - exptime, err := time.Parse(time.RFC1123, val) |
2891 | - if err != nil { |
2892 | - exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val) |
2893 | - if err != nil { |
2894 | - c.Expires = time.Time{} |
2895 | - break |
2896 | - } |
2897 | - } |
2898 | - c.Expires = exptime.UTC() |
2899 | - continue |
2900 | - case "path": |
2901 | - c.Path = val |
2902 | - // TODO: Add path parsing |
2903 | - continue |
2904 | - } |
2905 | - c.Unparsed = append(c.Unparsed, parts[i]) |
2906 | - } |
2907 | - cookies = append(cookies, c) |
2908 | - } |
2909 | - return cookies |
2910 | + attr, val := parts[i], "" |
2911 | + if j := strings.Index(attr, "="); j >= 0 { |
2912 | + attr, val = attr[:j], attr[j+1:] |
2913 | + } |
2914 | + lowerAttr := strings.ToLower(attr) |
2915 | + parseCookieValueFn := parseCookieValue |
2916 | + if lowerAttr == "expires" { |
2917 | + parseCookieValueFn = parseCookieExpiresValue |
2918 | + } |
2919 | + val, success = parseCookieValueFn(val) |
2920 | + if !success { |
2921 | + c.Unparsed = append(c.Unparsed, parts[i]) |
2922 | + continue |
2923 | + } |
2924 | + switch lowerAttr { |
2925 | + case "secure": |
2926 | + c.Secure = true |
2927 | + continue |
2928 | + case "httponly": |
2929 | + c.HttpOnly = true |
2930 | + continue |
2931 | + case "domain": |
2932 | + c.Domain = val |
2933 | + // TODO: Add domain parsing |
2934 | + continue |
2935 | + case "max-age": |
2936 | + secs, err := strconv.Atoi(val) |
2937 | + if err != nil || secs != 0 && val[0] == '0' { |
2938 | + break |
2939 | + } |
2940 | + if secs <= 0 { |
2941 | + c.MaxAge = -1 |
2942 | + } else { |
2943 | + c.MaxAge = secs |
2944 | + } |
2945 | + continue |
2946 | + case "expires": |
2947 | + c.RawExpires = val |
2948 | + exptime, err := time.Parse(time.RFC1123, val) |
2949 | + if err != nil { |
2950 | + exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val) |
2951 | + if err != nil { |
2952 | + c.Expires = time.Time{} |
2953 | + break |
2954 | + } |
2955 | + } |
2956 | + c.Expires = exptime.UTC() |
2957 | + continue |
2958 | + case "path": |
2959 | + c.Path = val |
2960 | + // TODO: Add path parsing |
2961 | + continue |
2962 | + } |
2963 | + c.Unparsed = append(c.Unparsed, parts[i]) |
2964 | + } |
2965 | + cookies = append(cookies, c) |
2966 | + } |
2967 | + return cookies |
2968 | } |
2969 | |
2970 | // SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers. |
2971 | func SetCookie(w ResponseWriter, cookie *Cookie) { |
2972 | - w.Header().Add("Set-Cookie", cookie.String()) |
2973 | + w.Header().Add("Set-Cookie", cookie.String()) |
2974 | } |
2975 | |
2976 | // String returns the serialization of the cookie for use in a Cookie |
2977 | // header (if only Name and Value are set) or a Set-Cookie response |
2978 | // header (if other fields are set). |
2979 | func (c *Cookie) String() string { |
2980 | - var b bytes.Buffer |
2981 | - fmt.Fprintf(&b, "%s=%s", sanitizeName(c.Name), sanitizeValue(c.Value)) |
2982 | - if len(c.Path) > 0 { |
2983 | - fmt.Fprintf(&b, "; Path=%s", sanitizeValue(c.Path)) |
2984 | - } |
2985 | - if len(c.Domain) > 0 { |
2986 | - fmt.Fprintf(&b, "; Domain=%s", sanitizeValue(c.Domain)) |
2987 | - } |
2988 | - if c.Expires.Unix() > 0 { |
2989 | - fmt.Fprintf(&b, "; Expires=%s", c.Expires.UTC().Format(time.RFC1123)) |
2990 | - } |
2991 | - if c.MaxAge > 0 { |
2992 | - fmt.Fprintf(&b, "; Max-Age=%d", c.MaxAge) |
2993 | - } else if c.MaxAge < 0 { |
2994 | - fmt.Fprintf(&b, "; Max-Age=0") |
2995 | - } |
2996 | - if c.HttpOnly { |
2997 | - fmt.Fprintf(&b, "; HttpOnly") |
2998 | - } |
2999 | - if c.Secure { |
3000 | - fmt.Fprintf(&b, "; Secure") |
3001 | - } |
3002 | - return b.String() |
3003 | + var b bytes.Buffer |
3004 | + fmt.Fprintf(&b, "%s=%s", sanitizeName(c.Name), sanitizeValue(c.Value)) |
3005 | + if len(c.Path) > 0 { |
3006 | + fmt.Fprintf(&b, "; Path=%s", sanitizeValue(c.Path)) |
3007 | + } |
3008 | + if len(c.Domain) > 0 { |
3009 | + fmt.Fprintf(&b, "; Domain=%s", sanitizeValue(c.Domain)) |
3010 | + } |
3011 | + if c.Expires.Unix() > 0 { |
3012 | + fmt.Fprintf(&b, "; Expires=%s", c.Expires.UTC().Format(time.RFC1123)) |
3013 | + } |
3014 | + if c.MaxAge > 0 { |
3015 | + fmt.Fprintf(&b, "; Max-Age=%d", c.MaxAge) |
3016 | + } else if c.MaxAge < 0 { |
3017 | + fmt.Fprintf(&b, "; Max-Age=0") |
3018 | + } |
3019 | + if c.HttpOnly { |
3020 | + fmt.Fprintf(&b, "; HttpOnly") |
3021 | + } |
3022 | + if c.Secure { |
3023 | + fmt.Fprintf(&b, "; Secure") |
3024 | + } |
3025 | + return b.String() |
3026 | } |
3027 | |
3028 | // readCookies parses all "Cookie" values from the header h and |
3029 | @@ -168,100 +168,100 @@ |
3030 | // |
3031 | // if filter isn't empty, only cookies of that name are returned |
3032 | func readCookies(h Header, filter string) []*Cookie { |
3033 | - cookies := []*Cookie{} |
3034 | - lines, ok := h["Cookie"] |
3035 | - if !ok { |
3036 | - return cookies |
3037 | - } |
3038 | + cookies := []*Cookie{} |
3039 | + lines, ok := h["Cookie"] |
3040 | + if !ok { |
3041 | + return cookies |
3042 | + } |
3043 | |
3044 | - for _, line := range lines { |
3045 | - parts := strings.Split(strings.TrimSpace(line), ";") |
3046 | - if len(parts) == 1 && parts[0] == "" { |
3047 | - continue |
3048 | - } |
3049 | - // Per-line attributes |
3050 | - parsedPairs := 0 |
3051 | - for i := 0; i < len(parts); i++ { |
3052 | - parts[i] = strings.TrimSpace(parts[i]) |
3053 | - if len(parts[i]) == 0 { |
3054 | - continue |
3055 | - } |
3056 | - name, val := parts[i], "" |
3057 | - if j := strings.Index(name, "="); j >= 0 { |
3058 | - name, val = name[:j], name[j+1:] |
3059 | - } |
3060 | - if !isCookieNameValid(name) { |
3061 | - continue |
3062 | - } |
3063 | - if filter != "" && filter != name { |
3064 | - continue |
3065 | - } |
3066 | - val, success := parseCookieValue(val) |
3067 | - if !success { |
3068 | - continue |
3069 | - } |
3070 | - cookies = append(cookies, &Cookie{Name: name, Value: val}) |
3071 | - parsedPairs++ |
3072 | - } |
3073 | - } |
3074 | - return cookies |
3075 | + for _, line := range lines { |
3076 | + parts := strings.Split(strings.TrimSpace(line), ";") |
3077 | + if len(parts) == 1 && parts[0] == "" { |
3078 | + continue |
3079 | + } |
3080 | + // Per-line attributes |
3081 | + parsedPairs := 0 |
3082 | + for i := 0; i < len(parts); i++ { |
3083 | + parts[i] = strings.TrimSpace(parts[i]) |
3084 | + if len(parts[i]) == 0 { |
3085 | + continue |
3086 | + } |
3087 | + name, val := parts[i], "" |
3088 | + if j := strings.Index(name, "="); j >= 0 { |
3089 | + name, val = name[:j], name[j+1:] |
3090 | + } |
3091 | + if !isCookieNameValid(name) { |
3092 | + continue |
3093 | + } |
3094 | + if filter != "" && filter != name { |
3095 | + continue |
3096 | + } |
3097 | + val, success := parseCookieValue(val) |
3098 | + if !success { |
3099 | + continue |
3100 | + } |
3101 | + cookies = append(cookies, &Cookie{Name: name, Value: val}) |
3102 | + parsedPairs++ |
3103 | + } |
3104 | + } |
3105 | + return cookies |
3106 | } |
3107 | |
3108 | var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-") |
3109 | |
3110 | func sanitizeName(n string) string { |
3111 | - return cookieNameSanitizer.Replace(n) |
3112 | + return cookieNameSanitizer.Replace(n) |
3113 | } |
3114 | |
3115 | var cookieValueSanitizer = strings.NewReplacer("\n", " ", "\r", " ", ";", " ") |
3116 | |
3117 | func sanitizeValue(v string) string { |
3118 | - return cookieValueSanitizer.Replace(v) |
3119 | + return cookieValueSanitizer.Replace(v) |
3120 | } |
3121 | |
3122 | func unquoteCookieValue(v string) string { |
3123 | - if len(v) > 1 && v[0] == '"' && v[len(v)-1] == '"' { |
3124 | - return v[1 : len(v)-1] |
3125 | - } |
3126 | - return v |
3127 | + if len(v) > 1 && v[0] == '"' && v[len(v)-1] == '"' { |
3128 | + return v[1 : len(v)-1] |
3129 | + } |
3130 | + return v |
3131 | } |
3132 | |
3133 | func isCookieByte(c byte) bool { |
3134 | - switch { |
3135 | - case c == 0x21, 0x23 <= c && c <= 0x2b, 0x2d <= c && c <= 0x3a, |
3136 | - 0x3c <= c && c <= 0x5b, 0x5d <= c && c <= 0x7e: |
3137 | - return true |
3138 | - } |
3139 | - return false |
3140 | + switch { |
3141 | + case c == 0x21, 0x23 <= c && c <= 0x2b, 0x2d <= c && c <= 0x3a, |
3142 | + 0x3c <= c && c <= 0x5b, 0x5d <= c && c <= 0x7e: |
3143 | + return true |
3144 | + } |
3145 | + return false |
3146 | } |
3147 | |
3148 | func isCookieExpiresByte(c byte) (ok bool) { |
3149 | - return isCookieByte(c) || c == ',' || c == ' ' |
3150 | + return isCookieByte(c) || c == ',' || c == ' ' |
3151 | } |
3152 | |
3153 | func parseCookieValue(raw string) (string, bool) { |
3154 | - return parseCookieValueUsing(raw, isCookieByte) |
3155 | + return parseCookieValueUsing(raw, isCookieByte) |
3156 | } |
3157 | |
3158 | func parseCookieExpiresValue(raw string) (string, bool) { |
3159 | - return parseCookieValueUsing(raw, isCookieExpiresByte) |
3160 | + return parseCookieValueUsing(raw, isCookieExpiresByte) |
3161 | } |
3162 | |
3163 | func parseCookieValueUsing(raw string, validByte func(byte) bool) (string, bool) { |
3164 | - raw = unquoteCookieValue(raw) |
3165 | - for i := 0; i < len(raw); i++ { |
3166 | - if !validByte(raw[i]) { |
3167 | - return "", false |
3168 | - } |
3169 | - } |
3170 | - return raw, true |
3171 | + raw = unquoteCookieValue(raw) |
3172 | + for i := 0; i < len(raw); i++ { |
3173 | + if !validByte(raw[i]) { |
3174 | + return "", false |
3175 | + } |
3176 | + } |
3177 | + return raw, true |
3178 | } |
3179 | |
3180 | func isCookieNameValid(raw string) bool { |
3181 | - for _, c := range raw { |
3182 | - if !isToken(byte(c)) { |
3183 | - return false |
3184 | - } |
3185 | - } |
3186 | - return true |
3187 | + for _, c := range raw { |
3188 | + if !isToken(byte(c)) { |
3189 | + return false |
3190 | + } |
3191 | + } |
3192 | + return true |
3193 | } |
3194 | |
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 | package http |
3200 | |
3201 | import ( |
3202 | - "fmt" |
3203 | - "io" |
3204 | + "fmt" |
3205 | + "io" |
3206 | ) |
3207 | |
3208 | // fileTransport implements RoundTripper for the 'file' protocol. |
3209 | type fileTransport struct { |
3210 | - fh fileHandler |
3211 | + fh fileHandler |
3212 | } |
3213 | |
3214 | // NewFileTransport returns a new RoundTripper, serving the provided |
3215 | @@ -28,38 +28,38 @@ |
3216 | // res, err := c.Get("file:///etc/passwd") |
3217 | // ... |
3218 | func NewFileTransport(fs FileSystem) RoundTripper { |
3219 | - return fileTransport{fileHandler{fs}} |
3220 | + return fileTransport{fileHandler{fs}} |
3221 | } |
3222 | |
3223 | func (t fileTransport) RoundTrip(req *Request) (resp *Response, err error) { |
3224 | - // We start ServeHTTP in a goroutine, which may take a long |
3225 | - // time if the file is large. The newPopulateResponseWriter |
3226 | - // call returns a channel which either ServeHTTP or finish() |
3227 | - // sends our *Response on, once the *Response itself has been |
3228 | - // populated (even if the body itself is still being |
3229 | - // written to the res.Body, a pipe) |
3230 | - rw, resc := newPopulateResponseWriter() |
3231 | - go func() { |
3232 | - t.fh.ServeHTTP(rw, req) |
3233 | - rw.finish() |
3234 | - }() |
3235 | - return <-resc, nil |
3236 | + // We start ServeHTTP in a goroutine, which may take a long |
3237 | + // time if the file is large. The newPopulateResponseWriter |
3238 | + // call returns a channel which either ServeHTTP or finish() |
3239 | + // sends our *Response on, once the *Response itself has been |
3240 | + // populated (even if the body itself is still being |
3241 | + // written to the res.Body, a pipe) |
3242 | + rw, resc := newPopulateResponseWriter() |
3243 | + go func() { |
3244 | + t.fh.ServeHTTP(rw, req) |
3245 | + rw.finish() |
3246 | + }() |
3247 | + return <-resc, nil |
3248 | } |
3249 | |
3250 | func newPopulateResponseWriter() (*populateResponse, <-chan *Response) { |
3251 | - pr, pw := io.Pipe() |
3252 | - rw := &populateResponse{ |
3253 | - ch: make(chan *Response), |
3254 | - pw: pw, |
3255 | - res: &Response{ |
3256 | - Proto: "HTTP/1.0", |
3257 | - ProtoMajor: 1, |
3258 | - Header: make(Header), |
3259 | - Close: true, |
3260 | - Body: pr, |
3261 | - }, |
3262 | - } |
3263 | - return rw, rw.ch |
3264 | + pr, pw := io.Pipe() |
3265 | + rw := &populateResponse{ |
3266 | + ch: make(chan *Response), |
3267 | + pw: pw, |
3268 | + res: &Response{ |
3269 | + Proto: "HTTP/1.0", |
3270 | + ProtoMajor: 1, |
3271 | + Header: make(Header), |
3272 | + Close: true, |
3273 | + Body: pr, |
3274 | + }, |
3275 | + } |
3276 | + return rw, rw.ch |
3277 | } |
3278 | |
3279 | // populateResponse is a ResponseWriter that populates the *Response |
3280 | @@ -67,57 +67,57 @@ |
3281 | // body. Once writes begin or finish() is called, the response is sent |
3282 | // on ch. |
3283 | type populateResponse struct { |
3284 | - res *Response |
3285 | - ch chan *Response |
3286 | - wroteHeader bool |
3287 | - hasContent bool |
3288 | - sentResponse bool |
3289 | - pw *io.PipeWriter |
3290 | + res *Response |
3291 | + ch chan *Response |
3292 | + wroteHeader bool |
3293 | + hasContent bool |
3294 | + sentResponse bool |
3295 | + pw *io.PipeWriter |
3296 | } |
3297 | |
3298 | func (pr *populateResponse) finish() { |
3299 | - if !pr.wroteHeader { |
3300 | - pr.WriteHeader(500) |
3301 | - } |
3302 | - if !pr.sentResponse { |
3303 | - pr.sendResponse() |
3304 | - } |
3305 | - pr.pw.Close() |
3306 | + if !pr.wroteHeader { |
3307 | + pr.WriteHeader(500) |
3308 | + } |
3309 | + if !pr.sentResponse { |
3310 | + pr.sendResponse() |
3311 | + } |
3312 | + pr.pw.Close() |
3313 | } |
3314 | |
3315 | func (pr *populateResponse) sendResponse() { |
3316 | - if pr.sentResponse { |
3317 | - return |
3318 | - } |
3319 | - pr.sentResponse = true |
3320 | + if pr.sentResponse { |
3321 | + return |
3322 | + } |
3323 | + pr.sentResponse = true |
3324 | |
3325 | - if pr.hasContent { |
3326 | - pr.res.ContentLength = -1 |
3327 | - } |
3328 | - pr.ch <- pr.res |
3329 | + if pr.hasContent { |
3330 | + pr.res.ContentLength = -1 |
3331 | + } |
3332 | + pr.ch <- pr.res |
3333 | } |
3334 | |
3335 | func (pr *populateResponse) Header() Header { |
3336 | - return pr.res.Header |
3337 | + return pr.res.Header |
3338 | } |
3339 | |
3340 | func (pr *populateResponse) WriteHeader(code int) { |
3341 | - if pr.wroteHeader { |
3342 | - return |
3343 | - } |
3344 | - pr.wroteHeader = true |
3345 | + if pr.wroteHeader { |
3346 | + return |
3347 | + } |
3348 | + pr.wroteHeader = true |
3349 | |
3350 | - pr.res.StatusCode = code |
3351 | - pr.res.Status = fmt.Sprintf("%d %s", code, StatusText(code)) |
3352 | + pr.res.StatusCode = code |
3353 | + pr.res.Status = fmt.Sprintf("%d %s", code, StatusText(code)) |
3354 | } |
3355 | |
3356 | func (pr *populateResponse) Write(p []byte) (n int, err error) { |
3357 | - if !pr.wroteHeader { |
3358 | - pr.WriteHeader(StatusOK) |
3359 | - } |
3360 | - pr.hasContent = true |
3361 | - if !pr.sentResponse { |
3362 | - pr.sendResponse() |
3363 | - } |
3364 | - return pr.pw.Write(p) |
3365 | + if !pr.wroteHeader { |
3366 | + pr.WriteHeader(StatusOK) |
3367 | + } |
3368 | + pr.hasContent = true |
3369 | + if !pr.sentResponse { |
3370 | + pr.sendResponse() |
3371 | + } |
3372 | + return pr.pw.Write(p) |
3373 | } |
3374 | |
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 | package http |
3380 | |
3381 | import ( |
3382 | - "errors" |
3383 | - "fmt" |
3384 | - "io" |
3385 | - "mime" |
3386 | - "os" |
3387 | - "path" |
3388 | - "path/filepath" |
3389 | - "strconv" |
3390 | - "strings" |
3391 | - "time" |
3392 | + "errors" |
3393 | + "fmt" |
3394 | + "io" |
3395 | + "mime" |
3396 | + "os" |
3397 | + "path" |
3398 | + "path/filepath" |
3399 | + "strconv" |
3400 | + "strings" |
3401 | + "time" |
3402 | ) |
3403 | |
3404 | // A Dir implements http.FileSystem using the native file |
3405 | @@ -26,55 +26,55 @@ |
3406 | type Dir string |
3407 | |
3408 | func (d Dir) Open(name string) (File, error) { |
3409 | - if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 { |
3410 | - return nil, errors.New("http: invalid character in file path") |
3411 | - } |
3412 | - dir := string(d) |
3413 | - if dir == "" { |
3414 | - dir = "." |
3415 | - } |
3416 | - f, err := os.Open(filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))) |
3417 | - if err != nil { |
3418 | - return nil, err |
3419 | - } |
3420 | - return f, nil |
3421 | + if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 { |
3422 | + return nil, errors.New("http: invalid character in file path") |
3423 | + } |
3424 | + dir := string(d) |
3425 | + if dir == "" { |
3426 | + dir = "." |
3427 | + } |
3428 | + f, err := os.Open(filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))) |
3429 | + if err != nil { |
3430 | + return nil, err |
3431 | + } |
3432 | + return f, nil |
3433 | } |
3434 | |
3435 | // A FileSystem implements access to a collection of named files. |
3436 | // The elements in a file path are separated by slash ('/', U+002F) |
3437 | // characters, regardless of host operating system convention. |
3438 | type FileSystem interface { |
3439 | - Open(name string) (File, error) |
3440 | + Open(name string) (File, error) |
3441 | } |
3442 | |
3443 | // A File is returned by a FileSystem's Open method and can be |
3444 | // served by the FileServer implementation. |
3445 | type File interface { |
3446 | - Close() error |
3447 | - Stat() (os.FileInfo, error) |
3448 | - Readdir(count int) ([]os.FileInfo, error) |
3449 | - Read([]byte) (int, error) |
3450 | - Seek(offset int64, whence int) (int64, error) |
3451 | + Close() error |
3452 | + Stat() (os.FileInfo, error) |
3453 | + Readdir(count int) ([]os.FileInfo, error) |
3454 | + Read([]byte) (int, error) |
3455 | + Seek(offset int64, whence int) (int64, error) |
3456 | } |
3457 | |
3458 | func dirList(w ResponseWriter, f File) { |
3459 | - w.Header().Set("Content-Type", "text/html; charset=utf-8") |
3460 | - fmt.Fprintf(w, "<pre>\n") |
3461 | - for { |
3462 | - dirs, err := f.Readdir(100) |
3463 | - if err != nil || len(dirs) == 0 { |
3464 | - break |
3465 | - } |
3466 | - for _, d := range dirs { |
3467 | - name := d.Name() |
3468 | - if d.IsDir() { |
3469 | - name += "/" |
3470 | - } |
3471 | - // TODO htmlescape |
3472 | - fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", name, name) |
3473 | - } |
3474 | - } |
3475 | - fmt.Fprintf(w, "</pre>\n") |
3476 | + w.Header().Set("Content-Type", "text/html; charset=utf-8") |
3477 | + fmt.Fprintf(w, "<pre>\n") |
3478 | + for { |
3479 | + dirs, err := f.Readdir(100) |
3480 | + if err != nil || len(dirs) == 0 { |
3481 | + break |
3482 | + } |
3483 | + for _, d := range dirs { |
3484 | + name := d.Name() |
3485 | + if d.IsDir() { |
3486 | + name += "/" |
3487 | + } |
3488 | + // TODO htmlescape |
3489 | + fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", name, name) |
3490 | + } |
3491 | + } |
3492 | + fmt.Fprintf(w, "</pre>\n") |
3493 | } |
3494 | |
3495 | // ServeContent replies to the request using the content in the |
3496 | @@ -99,192 +99,192 @@ |
3497 | // |
3498 | // Note that *os.File implements the io.ReadSeeker interface. |
3499 | func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker) { |
3500 | - size, err := content.Seek(0, os.SEEK_END) |
3501 | - if err != nil { |
3502 | - Error(w, "seeker can't seek", StatusInternalServerError) |
3503 | - return |
3504 | - } |
3505 | - _, err = content.Seek(0, os.SEEK_SET) |
3506 | - if err != nil { |
3507 | - Error(w, "seeker can't seek", StatusInternalServerError) |
3508 | - return |
3509 | - } |
3510 | - serveContent(w, req, name, modtime, size, content) |
3511 | + size, err := content.Seek(0, os.SEEK_END) |
3512 | + if err != nil { |
3513 | + Error(w, "seeker can't seek", StatusInternalServerError) |
3514 | + return |
3515 | + } |
3516 | + _, err = content.Seek(0, os.SEEK_SET) |
3517 | + if err != nil { |
3518 | + Error(w, "seeker can't seek", StatusInternalServerError) |
3519 | + return |
3520 | + } |
3521 | + serveContent(w, req, name, modtime, size, content) |
3522 | } |
3523 | |
3524 | // if name is empty, filename is unknown. (used for mime type, before sniffing) |
3525 | // if modtime.IsZero(), modtime is unknown. |
3526 | // content must be seeked to the beginning of the file. |
3527 | func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, size int64, content io.ReadSeeker) { |
3528 | - if checkLastModified(w, r, modtime) { |
3529 | - return |
3530 | - } |
3531 | - |
3532 | - code := StatusOK |
3533 | - |
3534 | - // If Content-Type isn't set, use the file's extension to find it. |
3535 | - if w.Header().Get("Content-Type") == "" { |
3536 | - ctype := mime.TypeByExtension(filepath.Ext(name)) |
3537 | - if ctype == "" { |
3538 | - // read a chunk to decide between utf-8 text and binary |
3539 | - var buf [1024]byte |
3540 | - n, _ := io.ReadFull(content, buf[:]) |
3541 | - b := buf[:n] |
3542 | - ctype = DetectContentType(b) |
3543 | - _, err := content.Seek(0, os.SEEK_SET) // rewind to output whole file |
3544 | - if err != nil { |
3545 | - Error(w, "seeker can't seek", StatusInternalServerError) |
3546 | - return |
3547 | - } |
3548 | - } |
3549 | - w.Header().Set("Content-Type", ctype) |
3550 | - } |
3551 | - |
3552 | - // handle Content-Range header. |
3553 | - // TODO(adg): handle multiple ranges |
3554 | - sendSize := size |
3555 | - if size >= 0 { |
3556 | - ranges, err := parseRange(r.Header.Get("Range"), size) |
3557 | - if err == nil && len(ranges) > 1 { |
3558 | - err = errors.New("multiple ranges not supported") |
3559 | - } |
3560 | - if err != nil { |
3561 | - Error(w, err.Error(), StatusRequestedRangeNotSatisfiable) |
3562 | - return |
3563 | - } |
3564 | - if len(ranges) == 1 { |
3565 | - ra := ranges[0] |
3566 | - if _, err := content.Seek(ra.start, os.SEEK_SET); err != nil { |
3567 | - Error(w, err.Error(), StatusRequestedRangeNotSatisfiable) |
3568 | - return |
3569 | - } |
3570 | - sendSize = ra.length |
3571 | - code = StatusPartialContent |
3572 | - w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, size)) |
3573 | - } |
3574 | - |
3575 | - w.Header().Set("Accept-Ranges", "bytes") |
3576 | - if w.Header().Get("Content-Encoding") == "" { |
3577 | - w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10)) |
3578 | - } |
3579 | - } |
3580 | - |
3581 | - w.WriteHeader(code) |
3582 | - |
3583 | - if r.Method != "HEAD" { |
3584 | - if sendSize == -1 { |
3585 | - io.Copy(w, content) |
3586 | - } else { |
3587 | - io.CopyN(w, content, sendSize) |
3588 | - } |
3589 | - } |
3590 | + if checkLastModified(w, r, modtime) { |
3591 | + return |
3592 | + } |
3593 | + |
3594 | + code := StatusOK |
3595 | + |
3596 | + // If Content-Type isn't set, use the file's extension to find it. |
3597 | + if w.Header().Get("Content-Type") == "" { |
3598 | + ctype := mime.TypeByExtension(filepath.Ext(name)) |
3599 | + if ctype == "" { |
3600 | + // read a chunk to decide between utf-8 text and binary |
3601 | + var buf [1024]byte |
3602 | + n, _ := io.ReadFull(content, buf[:]) |
3603 | + b := buf[:n] |
3604 | + ctype = DetectContentType(b) |
3605 | + _, err := content.Seek(0, os.SEEK_SET) // rewind to output whole file |
3606 | + if err != nil { |
3607 | + Error(w, "seeker can't seek", StatusInternalServerError) |
3608 | + return |
3609 | + } |
3610 | + } |
3611 | + w.Header().Set("Content-Type", ctype) |
3612 | + } |
3613 | + |
3614 | + // handle Content-Range header. |
3615 | + // TODO(adg): handle multiple ranges |
3616 | + sendSize := size |
3617 | + if size >= 0 { |
3618 | + ranges, err := parseRange(r.Header.Get("Range"), size) |
3619 | + if err == nil && len(ranges) > 1 { |
3620 | + err = errors.New("multiple ranges not supported") |
3621 | + } |
3622 | + if err != nil { |
3623 | + Error(w, err.Error(), StatusRequestedRangeNotSatisfiable) |
3624 | + return |
3625 | + } |
3626 | + if len(ranges) == 1 { |
3627 | + ra := ranges[0] |
3628 | + if _, err := content.Seek(ra.start, os.SEEK_SET); err != nil { |
3629 | + Error(w, err.Error(), StatusRequestedRangeNotSatisfiable) |
3630 | + return |
3631 | + } |
3632 | + sendSize = ra.length |
3633 | + code = StatusPartialContent |
3634 | + w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, size)) |
3635 | + } |
3636 | + |
3637 | + w.Header().Set("Accept-Ranges", "bytes") |
3638 | + if w.Header().Get("Content-Encoding") == "" { |
3639 | + w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10)) |
3640 | + } |
3641 | + } |
3642 | + |
3643 | + w.WriteHeader(code) |
3644 | + |
3645 | + if r.Method != "HEAD" { |
3646 | + if sendSize == -1 { |
3647 | + io.Copy(w, content) |
3648 | + } else { |
3649 | + io.CopyN(w, content, sendSize) |
3650 | + } |
3651 | + } |
3652 | } |
3653 | |
3654 | // modtime is the modification time of the resource to be served, or IsZero(). |
3655 | // return value is whether this request is now complete. |
3656 | func checkLastModified(w ResponseWriter, r *Request, modtime time.Time) bool { |
3657 | - if modtime.IsZero() { |
3658 | - return false |
3659 | - } |
3660 | + if modtime.IsZero() { |
3661 | + return false |
3662 | + } |
3663 | |
3664 | - // The Date-Modified header truncates sub-second precision, so |
3665 | - // use mtime < t+1s instead of mtime <= t to check for unmodified. |
3666 | - if t, err := time.Parse(TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && modtime.Before(t.Add(1*time.Second)) { |
3667 | - w.WriteHeader(StatusNotModified) |
3668 | - return true |
3669 | - } |
3670 | - w.Header().Set("Last-Modified", modtime.UTC().Format(TimeFormat)) |
3671 | - return false |
3672 | + // The Date-Modified header truncates sub-second precision, so |
3673 | + // use mtime < t+1s instead of mtime <= t to check for unmodified. |
3674 | + if t, err := time.Parse(TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && modtime.Before(t.Add(1*time.Second)) { |
3675 | + w.WriteHeader(StatusNotModified) |
3676 | + return true |
3677 | + } |
3678 | + w.Header().Set("Last-Modified", modtime.UTC().Format(TimeFormat)) |
3679 | + return false |
3680 | } |
3681 | |
3682 | // name is '/'-separated, not filepath.Separator. |
3683 | func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) { |
3684 | - const indexPage = "/index.html" |
3685 | - |
3686 | - // redirect .../index.html to .../ |
3687 | - // can't use Redirect() because that would make the path absolute, |
3688 | - // which would be a problem running under StripPrefix |
3689 | - if strings.HasSuffix(r.URL.Path, indexPage) { |
3690 | - localRedirect(w, r, "./") |
3691 | - return |
3692 | - } |
3693 | - |
3694 | - f, err := fs.Open(name) |
3695 | - if err != nil { |
3696 | - // TODO expose actual error? |
3697 | - NotFound(w, r) |
3698 | - return |
3699 | - } |
3700 | - defer f.Close() |
3701 | - |
3702 | - d, err1 := f.Stat() |
3703 | - if err1 != nil { |
3704 | - // TODO expose actual error? |
3705 | - NotFound(w, r) |
3706 | - return |
3707 | - } |
3708 | - |
3709 | - if redirect { |
3710 | - // redirect to canonical path: / at end of directory url |
3711 | - // r.URL.Path always begins with / |
3712 | - url := r.URL.Path |
3713 | - if d.IsDir() { |
3714 | - if url[len(url)-1] != '/' { |
3715 | - localRedirect(w, r, path.Base(url)+"/") |
3716 | - return |
3717 | - } |
3718 | - } else { |
3719 | - if url[len(url)-1] == '/' { |
3720 | - localRedirect(w, r, "../"+path.Base(url)) |
3721 | - return |
3722 | - } |
3723 | - } |
3724 | - } |
3725 | - |
3726 | - // use contents of index.html for directory, if present |
3727 | - if d.IsDir() { |
3728 | - if checkLastModified(w, r, d.ModTime()) { |
3729 | - return |
3730 | - } |
3731 | - index := name + indexPage |
3732 | - ff, err := fs.Open(index) |
3733 | - if err == nil { |
3734 | - defer ff.Close() |
3735 | - dd, err := ff.Stat() |
3736 | - if err == nil { |
3737 | - name = index |
3738 | - d = dd |
3739 | - f = ff |
3740 | - } |
3741 | - } |
3742 | - } |
3743 | - |
3744 | - if d.IsDir() { |
3745 | - dirList(w, f) |
3746 | - return |
3747 | - } |
3748 | - |
3749 | - serveContent(w, r, d.Name(), d.ModTime(), d.Size(), f) |
3750 | + const indexPage = "/index.html" |
3751 | + |
3752 | + // redirect .../index.html to .../ |
3753 | + // can't use Redirect() because that would make the path absolute, |
3754 | + // which would be a problem running under StripPrefix |
3755 | + if strings.HasSuffix(r.URL.Path, indexPage) { |
3756 | + localRedirect(w, r, "./") |
3757 | + return |
3758 | + } |
3759 | + |
3760 | + f, err := fs.Open(name) |
3761 | + if err != nil { |
3762 | + // TODO expose actual error? |
3763 | + NotFound(w, r) |
3764 | + return |
3765 | + } |
3766 | + defer f.Close() |
3767 | + |
3768 | + d, err1 := f.Stat() |
3769 | + if err1 != nil { |
3770 | + // TODO expose actual error? |
3771 | + NotFound(w, r) |
3772 | + return |
3773 | + } |
3774 | + |
3775 | + if redirect { |
3776 | + // redirect to canonical path: / at end of directory url |
3777 | + // r.URL.Path always begins with / |
3778 | + url := r.URL.Path |
3779 | + if d.IsDir() { |
3780 | + if url[len(url)-1] != '/' { |
3781 | + localRedirect(w, r, path.Base(url)+"/") |
3782 | + return |
3783 | + } |
3784 | + } else { |
3785 | + if url[len(url)-1] == '/' { |
3786 | + localRedirect(w, r, "../"+path.Base(url)) |
3787 | + return |
3788 | + } |
3789 | + } |
3790 | + } |
3791 | + |
3792 | + // use contents of index.html for directory, if present |
3793 | + if d.IsDir() { |
3794 | + if checkLastModified(w, r, d.ModTime()) { |
3795 | + return |
3796 | + } |
3797 | + index := name + indexPage |
3798 | + ff, err := fs.Open(index) |
3799 | + if err == nil { |
3800 | + defer ff.Close() |
3801 | + dd, err := ff.Stat() |
3802 | + if err == nil { |
3803 | + name = index |
3804 | + d = dd |
3805 | + f = ff |
3806 | + } |
3807 | + } |
3808 | + } |
3809 | + |
3810 | + if d.IsDir() { |
3811 | + dirList(w, f) |
3812 | + return |
3813 | + } |
3814 | + |
3815 | + serveContent(w, r, d.Name(), d.ModTime(), d.Size(), f) |
3816 | } |
3817 | |
3818 | // localRedirect gives a Moved Permanently response. |
3819 | // It does not convert relative paths to absolute paths like Redirect does. |
3820 | func localRedirect(w ResponseWriter, r *Request, newPath string) { |
3821 | - if q := r.URL.RawQuery; q != "" { |
3822 | - newPath += "?" + q |
3823 | - } |
3824 | - w.Header().Set("Location", newPath) |
3825 | - w.WriteHeader(StatusMovedPermanently) |
3826 | + if q := r.URL.RawQuery; q != "" { |
3827 | + newPath += "?" + q |
3828 | + } |
3829 | + w.Header().Set("Location", newPath) |
3830 | + w.WriteHeader(StatusMovedPermanently) |
3831 | } |
3832 | |
3833 | // ServeFile replies to the request with the contents of the named file or directory. |
3834 | func ServeFile(w ResponseWriter, r *Request, name string) { |
3835 | - dir, file := filepath.Split(name) |
3836 | - serveFile(w, r, Dir(dir), file, false) |
3837 | + dir, file := filepath.Split(name) |
3838 | + serveFile(w, r, Dir(dir), file, false) |
3839 | } |
3840 | |
3841 | type fileHandler struct { |
3842 | - root FileSystem |
3843 | + root FileSystem |
3844 | } |
3845 | |
3846 | // FileServer returns a handler that serves HTTP requests |
3847 | @@ -295,73 +295,73 @@ |
3848 | // |
3849 | // http.Handle("/", http.FileServer(http.Dir("/tmp"))) |
3850 | func FileServer(root FileSystem) Handler { |
3851 | - return &fileHandler{root} |
3852 | + return &fileHandler{root} |
3853 | } |
3854 | |
3855 | func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) { |
3856 | - upath := r.URL.Path |
3857 | - if !strings.HasPrefix(upath, "/") { |
3858 | - upath = "/" + upath |
3859 | - r.URL.Path = upath |
3860 | - } |
3861 | - serveFile(w, r, f.root, path.Clean(upath), true) |
3862 | + upath := r.URL.Path |
3863 | + if !strings.HasPrefix(upath, "/") { |
3864 | + upath = "/" + upath |
3865 | + r.URL.Path = upath |
3866 | + } |
3867 | + serveFile(w, r, f.root, path.Clean(upath), true) |
3868 | } |
3869 | |
3870 | // httpRange specifies the byte range to be sent to the client. |
3871 | type httpRange struct { |
3872 | - start, length int64 |
3873 | + start, length int64 |
3874 | } |
3875 | |
3876 | // parseRange parses a Range header string as per RFC 2616. |
3877 | func parseRange(s string, size int64) ([]httpRange, error) { |
3878 | - if s == "" { |
3879 | - return nil, nil // header not present |
3880 | - } |
3881 | - const b = "bytes=" |
3882 | - if !strings.HasPrefix(s, b) { |
3883 | - return nil, errors.New("invalid range") |
3884 | - } |
3885 | - var ranges []httpRange |
3886 | - for _, ra := range strings.Split(s[len(b):], ",") { |
3887 | - i := strings.Index(ra, "-") |
3888 | - if i < 0 { |
3889 | - return nil, errors.New("invalid range") |
3890 | - } |
3891 | - start, end := ra[:i], ra[i+1:] |
3892 | - var r httpRange |
3893 | - if start == "" { |
3894 | - // If no start is specified, end specifies the |
3895 | - // range start relative to the end of the file. |
3896 | - i, err := strconv.ParseInt(end, 10, 64) |
3897 | - if err != nil { |
3898 | - return nil, errors.New("invalid range") |
3899 | - } |
3900 | - if i > size { |
3901 | - i = size |
3902 | - } |
3903 | - r.start = size - i |
3904 | - r.length = size - r.start |
3905 | - } else { |
3906 | - i, err := strconv.ParseInt(start, 10, 64) |
3907 | - if err != nil || i > size || i < 0 { |
3908 | - return nil, errors.New("invalid range") |
3909 | - } |
3910 | - r.start = i |
3911 | - if end == "" { |
3912 | - // If no end is specified, range extends to end of the file. |
3913 | - r.length = size - r.start |
3914 | - } else { |
3915 | - i, err := strconv.ParseInt(end, 10, 64) |
3916 | - if err != nil || r.start > i { |
3917 | - return nil, errors.New("invalid range") |
3918 | - } |
3919 | - if i >= size { |
3920 | - i = size - 1 |
3921 | - } |
3922 | - r.length = i - r.start + 1 |
3923 | - } |
3924 | - } |
3925 | - ranges = append(ranges, r) |
3926 | - } |
3927 | - return ranges, nil |
3928 | + if s == "" { |
3929 | + return nil, nil // header not present |
3930 | + } |
3931 | + const b = "bytes=" |
3932 | + if !strings.HasPrefix(s, b) { |
3933 | + return nil, errors.New("invalid range") |
3934 | + } |
3935 | + var ranges []httpRange |
3936 | + for _, ra := range strings.Split(s[len(b):], ",") { |
3937 | + i := strings.Index(ra, "-") |
3938 | + if i < 0 { |
3939 | + return nil, errors.New("invalid range") |
3940 | + } |
3941 | + start, end := ra[:i], ra[i+1:] |
3942 | + var r httpRange |
3943 | + if start == "" { |
3944 | + // If no start is specified, end specifies the |
3945 | + // range start relative to the end of the file. |
3946 | + i, err := strconv.ParseInt(end, 10, 64) |
3947 | + if err != nil { |
3948 | + return nil, errors.New("invalid range") |
3949 | + } |
3950 | + if i > size { |
3951 | + i = size |
3952 | + } |
3953 | + r.start = size - i |
3954 | + r.length = size - r.start |
3955 | + } else { |
3956 | + i, err := strconv.ParseInt(start, 10, 64) |
3957 | + if err != nil || i > size || i < 0 { |
3958 | + return nil, errors.New("invalid range") |
3959 | + } |
3960 | + r.start = i |
3961 | + if end == "" { |
3962 | + // If no end is specified, range extends to end of the file. |
3963 | + r.length = size - r.start |
3964 | + } else { |
3965 | + i, err := strconv.ParseInt(end, 10, 64) |
3966 | + if err != nil || r.start > i { |
3967 | + return nil, errors.New("invalid range") |
3968 | + } |
3969 | + if i >= size { |
3970 | + i = size - 1 |
3971 | + } |
3972 | + r.length = i - r.start + 1 |
3973 | + } |
3974 | + } |
3975 | + ranges = append(ranges, r) |
3976 | + } |
3977 | + return ranges, nil |
3978 | } |
3979 | |
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 | package http |
3985 | |
3986 | import ( |
3987 | - "fmt" |
3988 | - "io" |
3989 | - "net/textproto" |
3990 | - "sort" |
3991 | - "strings" |
3992 | + "fmt" |
3993 | + "io" |
3994 | + "net/textproto" |
3995 | + "sort" |
3996 | + "strings" |
3997 | ) |
3998 | |
3999 | // A Header represents the key-value pairs in an HTTP header. |
4000 | @@ -18,14 +18,14 @@ |
4001 | // Add adds the key, value pair to the header. |
4002 | // It appends to any existing values associated with key. |
4003 | func (h Header) Add(key, value string) { |
4004 | - textproto.MIMEHeader(h).Add(key, value) |
4005 | + textproto.MIMEHeader(h).Add(key, value) |
4006 | } |
4007 | |
4008 | // Set sets the header entries associated with key to |
4009 | // the single element value. It replaces any existing |
4010 | // values associated with key. |
4011 | func (h Header) Set(key, value string) { |
4012 | - textproto.MIMEHeader(h).Set(key, value) |
4013 | + textproto.MIMEHeader(h).Set(key, value) |
4014 | } |
4015 | |
4016 | // Get gets the first value associated with the given key. |
4017 | @@ -33,17 +33,17 @@ |
4018 | // To access multiple values of a key, access the map directly |
4019 | // with CanonicalHeaderKey. |
4020 | func (h Header) Get(key string) string { |
4021 | - return textproto.MIMEHeader(h).Get(key) |
4022 | + return textproto.MIMEHeader(h).Get(key) |
4023 | } |
4024 | |
4025 | // Del deletes the values associated with key. |
4026 | func (h Header) Del(key string) { |
4027 | - textproto.MIMEHeader(h).Del(key) |
4028 | + textproto.MIMEHeader(h).Del(key) |
4029 | } |
4030 | |
4031 | // Write writes a header in wire format. |
4032 | func (h Header) Write(w io.Writer) error { |
4033 | - return h.WriteSubset(w, nil) |
4034 | + return h.WriteSubset(w, nil) |
4035 | } |
4036 | |
4037 | var headerNewlineToSpace = strings.NewReplacer("\n", " ", "\r", " ") |
4038 | @@ -51,23 +51,23 @@ |
4039 | // WriteSubset writes a header in wire format. |
4040 | // If exclude is not nil, keys where exclude[key] == true are not written. |
4041 | func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error { |
4042 | - keys := make([]string, 0, len(h)) |
4043 | - for k := range h { |
4044 | - if exclude == nil || !exclude[k] { |
4045 | - keys = append(keys, k) |
4046 | - } |
4047 | - } |
4048 | - sort.Strings(keys) |
4049 | - for _, k := range keys { |
4050 | - for _, v := range h[k] { |
4051 | - v = headerNewlineToSpace.Replace(v) |
4052 | - v = strings.TrimSpace(v) |
4053 | - if _, err := fmt.Fprintf(w, "%s: %s\r\n", k, v); err != nil { |
4054 | - return err |
4055 | - } |
4056 | - } |
4057 | - } |
4058 | - return nil |
4059 | + keys := make([]string, 0, len(h)) |
4060 | + for k := range h { |
4061 | + if exclude == nil || !exclude[k] { |
4062 | + keys = append(keys, k) |
4063 | + } |
4064 | + } |
4065 | + sort.Strings(keys) |
4066 | + for _, k := range keys { |
4067 | + for _, v := range h[k] { |
4068 | + v = headerNewlineToSpace.Replace(v) |
4069 | + v = strings.TrimSpace(v) |
4070 | + if _, err := fmt.Fprintf(w, "%s: %s\r\n", k, v); err != nil { |
4071 | + return err |
4072 | + } |
4073 | + } |
4074 | + } |
4075 | + return nil |
4076 | } |
4077 | |
4078 | // CanonicalHeaderKey returns the canonical format of the |
4079 | |
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 | package http |
4085 | |
4086 | import ( |
4087 | - "net/url" |
4088 | + "net/url" |
4089 | ) |
4090 | |
4091 | // A CookieJar manages storage and use of cookies in HTTP requests. |
4092 | @@ -13,15 +13,15 @@ |
4093 | // Implementations of CookieJar must be safe for concurrent use by multiple |
4094 | // goroutines. |
4095 | type CookieJar interface { |
4096 | - // SetCookies handles the receipt of the cookies in a reply for the |
4097 | - // given URL. It may or may not choose to save the cookies, depending |
4098 | - // on the jar's policy and implementation. |
4099 | - SetCookies(u *url.URL, cookies []*Cookie) |
4100 | + // SetCookies handles the receipt of the cookies in a reply for the |
4101 | + // given URL. It may or may not choose to save the cookies, depending |
4102 | + // on the jar's policy and implementation. |
4103 | + SetCookies(u *url.URL, cookies []*Cookie) |
4104 | |
4105 | - // Cookies returns the cookies to send in a request for the given URL. |
4106 | - // It is up to the implementation to honor the standard cookie use |
4107 | - // restrictions such as in RFC 6265. |
4108 | - Cookies(u *url.URL) []*Cookie |
4109 | + // Cookies returns the cookies to send in a request for the given URL. |
4110 | + // It is up to the implementation to honor the standard cookie use |
4111 | + // restrictions such as in RFC 6265. |
4112 | + Cookies(u *url.URL) []*Cookie |
4113 | } |
4114 | |
4115 | type blackHoleJar struct{} |
4116 | |
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 | // This file deals with lexical matters of HTTP |
4122 | |
4123 | func isSeparator(c byte) bool { |
4124 | - switch c { |
4125 | - case '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t': |
4126 | - return true |
4127 | - } |
4128 | - return false |
4129 | + switch c { |
4130 | + case '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t': |
4131 | + return true |
4132 | + } |
4133 | + return false |
4134 | } |
4135 | |
4136 | func isCtl(c byte) bool { return (0 <= c && c <= 31) || c == 127 } |
4137 | @@ -29,108 +29,108 @@ |
4138 | // characters should probably not be treated as errors by a robust (forgiving) |
4139 | // parser, so we replace them with the '?' character. |
4140 | func httpUnquotePair(b byte) byte { |
4141 | - // skip the first byte, which should always be '\' |
4142 | - switch b { |
4143 | - case 'a': |
4144 | - return '\a' |
4145 | - case 'b': |
4146 | - return '\b' |
4147 | - case 'f': |
4148 | - return '\f' |
4149 | - case 'n': |
4150 | - return '\n' |
4151 | - case 'r': |
4152 | - return '\r' |
4153 | - case 't': |
4154 | - return '\t' |
4155 | - case 'v': |
4156 | - return '\v' |
4157 | - case '\\': |
4158 | - return '\\' |
4159 | - case '\'': |
4160 | - return '\'' |
4161 | - case '"': |
4162 | - return '"' |
4163 | - } |
4164 | - return '?' |
4165 | + // skip the first byte, which should always be '\' |
4166 | + switch b { |
4167 | + case 'a': |
4168 | + return '\a' |
4169 | + case 'b': |
4170 | + return '\b' |
4171 | + case 'f': |
4172 | + return '\f' |
4173 | + case 'n': |
4174 | + return '\n' |
4175 | + case 'r': |
4176 | + return '\r' |
4177 | + case 't': |
4178 | + return '\t' |
4179 | + case 'v': |
4180 | + return '\v' |
4181 | + case '\\': |
4182 | + return '\\' |
4183 | + case '\'': |
4184 | + return '\'' |
4185 | + case '"': |
4186 | + return '"' |
4187 | + } |
4188 | + return '?' |
4189 | } |
4190 | |
4191 | // raw must begin with a valid quoted string. Only the first quoted string is |
4192 | // parsed and is unquoted in result. eaten is the number of bytes parsed, or -1 |
4193 | // upon failure. |
4194 | func httpUnquote(raw []byte) (eaten int, result string) { |
4195 | - buf := make([]byte, len(raw)) |
4196 | - if raw[0] != '"' { |
4197 | - return -1, "" |
4198 | - } |
4199 | - eaten = 1 |
4200 | - j := 0 // # of bytes written in buf |
4201 | - for i := 1; i < len(raw); i++ { |
4202 | - switch b := raw[i]; b { |
4203 | - case '"': |
4204 | - eaten++ |
4205 | - buf = buf[0:j] |
4206 | - return i + 1, string(buf) |
4207 | - case '\\': |
4208 | - if len(raw) < i+2 { |
4209 | - return -1, "" |
4210 | - } |
4211 | - buf[j] = httpUnquotePair(raw[i+1]) |
4212 | - eaten += 2 |
4213 | - j++ |
4214 | - i++ |
4215 | - default: |
4216 | - if isQdText(b) { |
4217 | - buf[j] = b |
4218 | - } else { |
4219 | - buf[j] = '?' |
4220 | - } |
4221 | - eaten++ |
4222 | - j++ |
4223 | - } |
4224 | - } |
4225 | - return -1, "" |
4226 | + buf := make([]byte, len(raw)) |
4227 | + if raw[0] != '"' { |
4228 | + return -1, "" |
4229 | + } |
4230 | + eaten = 1 |
4231 | + j := 0 // # of bytes written in buf |
4232 | + for i := 1; i < len(raw); i++ { |
4233 | + switch b := raw[i]; b { |
4234 | + case '"': |
4235 | + eaten++ |
4236 | + buf = buf[0:j] |
4237 | + return i + 1, string(buf) |
4238 | + case '\\': |
4239 | + if len(raw) < i+2 { |
4240 | + return -1, "" |
4241 | + } |
4242 | + buf[j] = httpUnquotePair(raw[i+1]) |
4243 | + eaten += 2 |
4244 | + j++ |
4245 | + i++ |
4246 | + default: |
4247 | + if isQdText(b) { |
4248 | + buf[j] = b |
4249 | + } else { |
4250 | + buf[j] = '?' |
4251 | + } |
4252 | + eaten++ |
4253 | + j++ |
4254 | + } |
4255 | + } |
4256 | + return -1, "" |
4257 | } |
4258 | |
4259 | // This is a best effort parse, so errors are not returned, instead not all of |
4260 | // the input string might be parsed. result is always non-nil. |
4261 | func httpSplitFieldValue(fv string) (eaten int, result []string) { |
4262 | - result = make([]string, 0, len(fv)) |
4263 | - raw := []byte(fv) |
4264 | - i := 0 |
4265 | - chunk := "" |
4266 | - for i < len(raw) { |
4267 | - b := raw[i] |
4268 | - switch { |
4269 | - case b == '"': |
4270 | - eaten, unq := httpUnquote(raw[i:]) |
4271 | - if eaten < 0 { |
4272 | - return i, result |
4273 | - } else { |
4274 | - i += eaten |
4275 | - chunk += unq |
4276 | - } |
4277 | - case isSeparator(b): |
4278 | - if chunk != "" { |
4279 | - result = result[0 : len(result)+1] |
4280 | - result[len(result)-1] = chunk |
4281 | - chunk = "" |
4282 | - } |
4283 | - i++ |
4284 | - case isToken(b): |
4285 | - chunk += string(b) |
4286 | - i++ |
4287 | - case b == '\n' || b == '\r': |
4288 | - i++ |
4289 | - default: |
4290 | - chunk += "?" |
4291 | - i++ |
4292 | - } |
4293 | - } |
4294 | - if chunk != "" { |
4295 | - result = result[0 : len(result)+1] |
4296 | - result[len(result)-1] = chunk |
4297 | - chunk = "" |
4298 | - } |
4299 | - return i, result |
4300 | + result = make([]string, 0, len(fv)) |
4301 | + raw := []byte(fv) |
4302 | + i := 0 |
4303 | + chunk := "" |
4304 | + for i < len(raw) { |
4305 | + b := raw[i] |
4306 | + switch { |
4307 | + case b == '"': |
4308 | + eaten, unq := httpUnquote(raw[i:]) |
4309 | + if eaten < 0 { |
4310 | + return i, result |
4311 | + } else { |
4312 | + i += eaten |
4313 | + chunk += unq |
4314 | + } |
4315 | + case isSeparator(b): |
4316 | + if chunk != "" { |
4317 | + result = result[0 : len(result)+1] |
4318 | + result[len(result)-1] = chunk |
4319 | + chunk = "" |
4320 | + } |
4321 | + i++ |
4322 | + case isToken(b): |
4323 | + chunk += string(b) |
4324 | + i++ |
4325 | + case b == '\n' || b == '\r': |
4326 | + i++ |
4327 | + default: |
4328 | + chunk += "?" |
4329 | + i++ |
4330 | + } |
4331 | + } |
4332 | + if chunk != "" { |
4333 | + result = result[0 : len(result)+1] |
4334 | + result[len(result)-1] = chunk |
4335 | + chunk = "" |
4336 | + } |
4337 | + return i, result |
4338 | } |
4339 | |
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 | package http |
4345 | |
4346 | import ( |
4347 | - "bufio" |
4348 | - "bytes" |
4349 | - "crypto/tls" |
4350 | - "encoding/base64" |
4351 | - "errors" |
4352 | - "fmt" |
4353 | - "io" |
4354 | - "io/ioutil" |
4355 | - "mime" |
4356 | - "mime/multipart" |
4357 | - "net/textproto" |
4358 | - "net/url" |
4359 | - "strings" |
4360 | + "bufio" |
4361 | + "bytes" |
4362 | + "crypto/tls" |
4363 | + "encoding/base64" |
4364 | + "errors" |
4365 | + "fmt" |
4366 | + "io" |
4367 | + "io/ioutil" |
4368 | + "mime" |
4369 | + "mime/multipart" |
4370 | + "net/textproto" |
4371 | + "net/url" |
4372 | + "strings" |
4373 | ) |
4374 | |
4375 | const ( |
4376 | - maxValueLength = 4096 |
4377 | - maxHeaderLines = 1024 |
4378 | - chunkSize = 4 << 10 // 4 KB chunks |
4379 | - defaultMaxMemory = 32 << 20 // 32 MB |
4380 | + maxValueLength = 4096 |
4381 | + maxHeaderLines = 1024 |
4382 | + chunkSize = 4 << 10 // 4 KB chunks |
4383 | + defaultMaxMemory = 32 << 20 // 32 MB |
4384 | ) |
4385 | |
4386 | // ErrMissingFile is returned by FormFile when the provided file field name |
4387 | @@ -35,155 +35,155 @@ |
4388 | |
4389 | // HTTP request parsing errors. |
4390 | type ProtocolError struct { |
4391 | - ErrorString string |
4392 | + ErrorString string |
4393 | } |
4394 | |
4395 | func (err *ProtocolError) Error() string { return err.ErrorString } |
4396 | |
4397 | var ( |
4398 | - ErrHeaderTooLong = &ProtocolError{"header too long"} |
4399 | - ErrShortBody = &ProtocolError{"entity body too short"} |
4400 | - ErrNotSupported = &ProtocolError{"feature not supported"} |
4401 | - ErrUnexpectedTrailer = &ProtocolError{"trailer header without chunked transfer encoding"} |
4402 | - ErrMissingContentLength = &ProtocolError{"missing ContentLength in HEAD response"} |
4403 | - ErrNotMultipart = &ProtocolError{"request Content-Type isn't multipart/form-data"} |
4404 | - ErrMissingBoundary = &ProtocolError{"no multipart boundary param Content-Type"} |
4405 | + ErrHeaderTooLong = &ProtocolError{"header too long"} |
4406 | + ErrShortBody = &ProtocolError{"entity body too short"} |
4407 | + ErrNotSupported = &ProtocolError{"feature not supported"} |
4408 | + ErrUnexpectedTrailer = &ProtocolError{"trailer header without chunked transfer encoding"} |
4409 | + ErrMissingContentLength = &ProtocolError{"missing ContentLength in HEAD response"} |
4410 | + ErrNotMultipart = &ProtocolError{"request Content-Type isn't multipart/form-data"} |
4411 | + ErrMissingBoundary = &ProtocolError{"no multipart boundary param Content-Type"} |
4412 | ) |
4413 | |
4414 | type badStringError struct { |
4415 | - what string |
4416 | - str string |
4417 | + what string |
4418 | + str string |
4419 | } |
4420 | |
4421 | func (e *badStringError) Error() string { return fmt.Sprintf("%s %q", e.what, e.str) } |
4422 | |
4423 | // Headers that Request.Write handles itself and should be skipped. |
4424 | var reqWriteExcludeHeader = map[string]bool{ |
4425 | - "Host": true, // not in Header map anyway |
4426 | - "User-Agent": true, |
4427 | - "Content-Length": true, |
4428 | - "Transfer-Encoding": true, |
4429 | - "Trailer": true, |
4430 | + "Host": true, // not in Header map anyway |
4431 | + "User-Agent": true, |
4432 | + "Content-Length": true, |
4433 | + "Transfer-Encoding": true, |
4434 | + "Trailer": true, |
4435 | } |
4436 | |
4437 | // A Request represents an HTTP request received by a server |
4438 | // or to be sent by a client. |
4439 | type Request struct { |
4440 | - Method string // GET, POST, PUT, etc. |
4441 | - URL *url.URL |
4442 | - |
4443 | - // The protocol version for incoming requests. |
4444 | - // Outgoing requests always use HTTP/1.1. |
4445 | - Proto string // "HTTP/1.0" |
4446 | - ProtoMajor int // 1 |
4447 | - ProtoMinor int // 0 |
4448 | - |
4449 | - // A header maps request lines to their values. |
4450 | - // If the header says |
4451 | - // |
4452 | - // accept-encoding: gzip, deflate |
4453 | - // Accept-Language: en-us |
4454 | - // Connection: keep-alive |
4455 | - // |
4456 | - // then |
4457 | - // |
4458 | - // Header = map[string][]string{ |
4459 | - // "Accept-Encoding": {"gzip, deflate"}, |
4460 | - // "Accept-Language": {"en-us"}, |
4461 | - // "Connection": {"keep-alive"}, |
4462 | - // } |
4463 | - // |
4464 | - // HTTP defines that header names are case-insensitive. |
4465 | - // The request parser implements this by canonicalizing the |
4466 | - // name, making the first character and any characters |
4467 | - // following a hyphen uppercase and the rest lowercase. |
4468 | - Header Header |
4469 | - |
4470 | - // The message body. |
4471 | - Body io.ReadCloser |
4472 | - |
4473 | - // ContentLength records the length of the associated content. |
4474 | - // The value -1 indicates that the length is unknown. |
4475 | - // Values >= 0 indicate that the given number of bytes may |
4476 | - // be read from Body. |
4477 | - // For outgoing requests, a value of 0 means unknown if Body is not nil. |
4478 | - ContentLength int64 |
4479 | - |
4480 | - // TransferEncoding lists the transfer encodings from outermost to |
4481 | - // innermost. An empty list denotes the "identity" encoding. |
4482 | - // TransferEncoding can usually be ignored; chunked encoding is |
4483 | - // automatically added and removed as necessary when sending and |
4484 | - // receiving requests. |
4485 | - TransferEncoding []string |
4486 | - |
4487 | - // Close indicates whether to close the connection after |
4488 | - // replying to this request. |
4489 | - Close bool |
4490 | - |
4491 | - // The host on which the URL is sought. |
4492 | - // Per RFC 2616, this is either the value of the Host: header |
4493 | - // or the host name given in the URL itself. |
4494 | - Host string |
4495 | - |
4496 | - // Form contains the parsed form data, including both the URL |
4497 | - // field's query parameters and the POST or PUT form data. |
4498 | - // This field is only available after ParseForm is called. |
4499 | - // The HTTP client ignores Form and uses Body instead. |
4500 | - Form url.Values |
4501 | - |
4502 | - // MultipartForm is the parsed multipart form, including file uploads. |
4503 | - // This field is only available after ParseMultipartForm is called. |
4504 | - // The HTTP client ignores MultipartForm and uses Body instead. |
4505 | - MultipartForm *multipart.Form |
4506 | - |
4507 | - // Trailer maps trailer keys to values. Like for Header, if the |
4508 | - // response has multiple trailer lines with the same key, they will be |
4509 | - // concatenated, delimited by commas. |
4510 | - // For server requests, Trailer is only populated after Body has been |
4511 | - // closed or fully consumed. |
4512 | - // Trailer support is only partially complete. |
4513 | - Trailer Header |
4514 | - |
4515 | - // RemoteAddr allows HTTP servers and other software to record |
4516 | - // the network address that sent the request, usually for |
4517 | - // logging. This field is not filled in by ReadRequest and |
4518 | - // has no defined format. The HTTP server in this package |
4519 | - // sets RemoteAddr to an "IP:port" address before invoking a |
4520 | - // handler. |
4521 | - // This field is ignored by the HTTP client. |
4522 | - RemoteAddr string |
4523 | - |
4524 | - // RequestURI is the unmodified Request-URI of the |
4525 | - // Request-Line (RFC 2616, Section 5.1) as sent by the client |
4526 | - // to a server. Usually the URL field should be used instead. |
4527 | - // It is an error to set this field in an HTTP client request. |
4528 | - RequestURI string |
4529 | - |
4530 | - // TLS allows HTTP servers and other software to record |
4531 | - // information about the TLS connection on which the request |
4532 | - // was received. This field is not filled in by ReadRequest. |
4533 | - // The HTTP server in this package sets the field for |
4534 | - // TLS-enabled connections before invoking a handler; |
4535 | - // otherwise it leaves the field nil. |
4536 | - // This field is ignored by the HTTP client. |
4537 | - TLS *tls.ConnectionState |
4538 | + Method string // GET, POST, PUT, etc. |
4539 | + URL *url.URL |
4540 | + |
4541 | + // The protocol version for incoming requests. |
4542 | + // Outgoing requests always use HTTP/1.1. |
4543 | + Proto string // "HTTP/1.0" |
4544 | + ProtoMajor int // 1 |
4545 | + ProtoMinor int // 0 |
4546 | + |
4547 | + // A header maps request lines to their values. |
4548 | + // If the header says |
4549 | + // |
4550 | + // accept-encoding: gzip, deflate |
4551 | + // Accept-Language: en-us |
4552 | + // Connection: keep-alive |
4553 | + // |
4554 | + // then |
4555 | + // |
4556 | + // Header = map[string][]string{ |
4557 | + // "Accept-Encoding": {"gzip, deflate"}, |
4558 | + // "Accept-Language": {"en-us"}, |
4559 | + // "Connection": {"keep-alive"}, |
4560 | + // } |
4561 | + // |
4562 | + // HTTP defines that header names are case-insensitive. |
4563 | + // The request parser implements this by canonicalizing the |
4564 | + // name, making the first character and any characters |
4565 | + // following a hyphen uppercase and the rest lowercase. |
4566 | + Header Header |
4567 | + |
4568 | + // The message body. |
4569 | + Body io.ReadCloser |
4570 | + |
4571 | + // ContentLength records the length of the associated content. |
4572 | + // The value -1 indicates that the length is unknown. |
4573 | + // Values >= 0 indicate that the given number of bytes may |
4574 | + // be read from Body. |
4575 | + // For outgoing requests, a value of 0 means unknown if Body is not nil. |
4576 | + ContentLength int64 |
4577 | + |
4578 | + // TransferEncoding lists the transfer encodings from outermost to |
4579 | + // innermost. An empty list denotes the "identity" encoding. |
4580 | + // TransferEncoding can usually be ignored; chunked encoding is |
4581 | + // automatically added and removed as necessary when sending and |
4582 | + // receiving requests. |
4583 | + TransferEncoding []string |
4584 | + |
4585 | + // Close indicates whether to close the connection after |
4586 | + // replying to this request. |
4587 | + Close bool |
4588 | + |
4589 | + // The host on which the URL is sought. |
4590 | + // Per RFC 2616, this is either the value of the Host: header |
4591 | + // or the host name given in the URL itself. |
4592 | + Host string |
4593 | + |
4594 | + // Form contains the parsed form data, including both the URL |
4595 | + // field's query parameters and the POST or PUT form data. |
4596 | + // This field is only available after ParseForm is called. |
4597 | + // The HTTP client ignores Form and uses Body instead. |
4598 | + Form url.Values |
4599 | + |
4600 | + // MultipartForm is the parsed multipart form, including file uploads. |
4601 | + // This field is only available after ParseMultipartForm is called. |
4602 | + // The HTTP client ignores MultipartForm and uses Body instead. |
4603 | + MultipartForm *multipart.Form |
4604 | + |
4605 | + // Trailer maps trailer keys to values. Like for Header, if the |
4606 | + // response has multiple trailer lines with the same key, they will be |
4607 | + // concatenated, delimited by commas. |
4608 | + // For server requests, Trailer is only populated after Body has been |
4609 | + // closed or fully consumed. |
4610 | + // Trailer support is only partially complete. |
4611 | + Trailer Header |
4612 | + |
4613 | + // RemoteAddr allows HTTP servers and other software to record |
4614 | + // the network address that sent the request, usually for |
4615 | + // logging. This field is not filled in by ReadRequest and |
4616 | + // has no defined format. The HTTP server in this package |
4617 | + // sets RemoteAddr to an "IP:port" address before invoking a |
4618 | + // handler. |
4619 | + // This field is ignored by the HTTP client. |
4620 | + RemoteAddr string |
4621 | + |
4622 | + // RequestURI is the unmodified Request-URI of the |
4623 | + // Request-Line (RFC 2616, Section 5.1) as sent by the client |
4624 | + // to a server. Usually the URL field should be used instead. |
4625 | + // It is an error to set this field in an HTTP client request. |
4626 | + RequestURI string |
4627 | + |
4628 | + // TLS allows HTTP servers and other software to record |
4629 | + // information about the TLS connection on which the request |
4630 | + // was received. This field is not filled in by ReadRequest. |
4631 | + // The HTTP server in this package sets the field for |
4632 | + // TLS-enabled connections before invoking a handler; |
4633 | + // otherwise it leaves the field nil. |
4634 | + // This field is ignored by the HTTP client. |
4635 | + TLS *tls.ConnectionState |
4636 | } |
4637 | |
4638 | // ProtoAtLeast returns whether the HTTP protocol used |
4639 | // in the request is at least major.minor. |
4640 | func (r *Request) ProtoAtLeast(major, minor int) bool { |
4641 | - return r.ProtoMajor > major || |
4642 | - r.ProtoMajor == major && r.ProtoMinor >= minor |
4643 | + return r.ProtoMajor > major || |
4644 | + r.ProtoMajor == major && r.ProtoMinor >= minor |
4645 | } |
4646 | |
4647 | // UserAgent returns the client's User-Agent, if sent in the request. |
4648 | func (r *Request) UserAgent() string { |
4649 | - return r.Header.Get("User-Agent") |
4650 | + return r.Header.Get("User-Agent") |
4651 | } |
4652 | |
4653 | // Cookies parses and returns the HTTP cookies sent with the request. |
4654 | func (r *Request) Cookies() []*Cookie { |
4655 | - return readCookies(r.Header, "") |
4656 | + return readCookies(r.Header, "") |
4657 | } |
4658 | |
4659 | var ErrNoCookie = errors.New("http: named cookie not present") |
4660 | @@ -191,10 +191,10 @@ |
4661 | // Cookie returns the named cookie provided in the request or |
4662 | // ErrNoCookie if not found. |
4663 | func (r *Request) Cookie(name string) (*Cookie, error) { |
4664 | - for _, c := range readCookies(r.Header, name) { |
4665 | - return c, nil |
4666 | - } |
4667 | - return nil, ErrNoCookie |
4668 | + for _, c := range readCookies(r.Header, name) { |
4669 | + return c, nil |
4670 | + } |
4671 | + return nil, ErrNoCookie |
4672 | } |
4673 | |
4674 | // AddCookie adds a cookie to the request. Per RFC 6265 section 5.4, |
4675 | @@ -202,12 +202,12 @@ |
4676 | // means all cookies, if any, are written into the same line, |
4677 | // separated by semicolon. |
4678 | func (r *Request) AddCookie(c *Cookie) { |
4679 | - s := fmt.Sprintf("%s=%s", sanitizeName(c.Name), sanitizeValue(c.Value)) |
4680 | - if c := r.Header.Get("Cookie"); c != "" { |
4681 | - r.Header.Set("Cookie", c+"; "+s) |
4682 | - } else { |
4683 | - r.Header.Set("Cookie", s) |
4684 | - } |
4685 | + s := fmt.Sprintf("%s=%s", sanitizeName(c.Name), sanitizeValue(c.Value)) |
4686 | + if c := r.Header.Get("Cookie"); c != "" { |
4687 | + r.Header.Set("Cookie", c+"; "+s) |
4688 | + } else { |
4689 | + r.Header.Set("Cookie", s) |
4690 | + } |
4691 | } |
4692 | |
4693 | // Referer returns the referring URL, if sent in the request. |
4694 | @@ -219,15 +219,15 @@ |
4695 | // alternate (correct English) spelling req.Referrer() but cannot |
4696 | // diagnose programs that use Header["Referrer"]. |
4697 | func (r *Request) Referer() string { |
4698 | - return r.Header.Get("Referer") |
4699 | + return r.Header.Get("Referer") |
4700 | } |
4701 | |
4702 | // multipartByReader is a sentinel value. |
4703 | // Its presence in Request.MultipartForm indicates that parsing of the request |
4704 | // body has been handed off to a MultipartReader instead of ParseMultipartFrom. |
4705 | var multipartByReader = &multipart.Form{ |
4706 | - Value: make(map[string][]string), |
4707 | - File: make(map[string][]*multipart.FileHeader), |
4708 | + Value: make(map[string][]string), |
4709 | + File: make(map[string][]*multipart.FileHeader), |
4710 | } |
4711 | |
4712 | // MultipartReader returns a MIME multipart reader if this is a |
4713 | @@ -235,38 +235,38 @@ |
4714 | // Use this function instead of ParseMultipartForm to |
4715 | // process the request body as a stream. |
4716 | func (r *Request) MultipartReader() (*multipart.Reader, error) { |
4717 | - if r.MultipartForm == multipartByReader { |
4718 | - return nil, errors.New("http: MultipartReader called twice") |
4719 | - } |
4720 | - if r.MultipartForm != nil { |
4721 | - return nil, errors.New("http: multipart handled by ParseMultipartForm") |
4722 | - } |
4723 | - r.MultipartForm = multipartByReader |
4724 | - return r.multipartReader() |
4725 | + if r.MultipartForm == multipartByReader { |
4726 | + return nil, errors.New("http: MultipartReader called twice") |
4727 | + } |
4728 | + if r.MultipartForm != nil { |
4729 | + return nil, errors.New("http: multipart handled by ParseMultipartForm") |
4730 | + } |
4731 | + r.MultipartForm = multipartByReader |
4732 | + return r.multipartReader() |
4733 | } |
4734 | |
4735 | func (r *Request) multipartReader() (*multipart.Reader, error) { |
4736 | - v := r.Header.Get("Content-Type") |
4737 | - if v == "" { |
4738 | - return nil, ErrNotMultipart |
4739 | - } |
4740 | - d, params, err := mime.ParseMediaType(v) |
4741 | - if err != nil || d != "multipart/form-data" { |
4742 | - return nil, ErrNotMultipart |
4743 | - } |
4744 | - boundary, ok := params["boundary"] |
4745 | - if !ok { |
4746 | - return nil, ErrMissingBoundary |
4747 | - } |
4748 | - return multipart.NewReader(r.Body, boundary), nil |
4749 | + v := r.Header.Get("Content-Type") |
4750 | + if v == "" { |
4751 | + return nil, ErrNotMultipart |
4752 | + } |
4753 | + d, params, err := mime.ParseMediaType(v) |
4754 | + if err != nil || d != "multipart/form-data" { |
4755 | + return nil, ErrNotMultipart |
4756 | + } |
4757 | + boundary, ok := params["boundary"] |
4758 | + if !ok { |
4759 | + return nil, ErrMissingBoundary |
4760 | + } |
4761 | + return multipart.NewReader(r.Body, boundary), nil |
4762 | } |
4763 | |
4764 | // Return value if nonempty, def otherwise. |
4765 | func valueOrDefault(value, def string) string { |
4766 | - if value != "" { |
4767 | - return value |
4768 | - } |
4769 | - return def |
4770 | + if value != "" { |
4771 | + return value |
4772 | + } |
4773 | + return def |
4774 | } |
4775 | |
4776 | const defaultUserAgent = "Go http package" |
4777 | @@ -285,7 +285,7 @@ |
4778 | // hasn't been set to "identity", Write adds "Transfer-Encoding: |
4779 | // chunked" to the header. Body is closed after it is sent. |
4780 | func (r *Request) Write(w io.Writer) error { |
4781 | - return r.write(w, false, nil) |
4782 | + return r.write(w, false, nil) |
4783 | } |
4784 | |
4785 | // WriteProxy is like Write but writes the request in the form |
4786 | @@ -295,145 +295,145 @@ |
4787 | // In either case, WriteProxy also writes a Host header, using |
4788 | // either r.Host or r.URL.Host. |
4789 | func (r *Request) WriteProxy(w io.Writer) error { |
4790 | - return r.write(w, true, nil) |
4791 | + return r.write(w, true, nil) |
4792 | } |
4793 | |
4794 | // extraHeaders may be nil |
4795 | func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders Header) error { |
4796 | - host := req.Host |
4797 | - if host == "" { |
4798 | - if req.URL == nil { |
4799 | - return errors.New("http: Request.Write on Request with no Host or URL set") |
4800 | - } |
4801 | - host = req.URL.Host |
4802 | - } |
4803 | - |
4804 | - ruri := req.URL.RequestURI() |
4805 | - if usingProxy && req.URL.Scheme != "" && req.URL.Opaque == "" { |
4806 | - ruri = req.URL.Scheme + "://" + host + ruri |
4807 | - } else if req.Method == "CONNECT" && req.URL.Path == "" { |
4808 | - // CONNECT requests normally give just the host and port, not a full URL. |
4809 | - ruri = host |
4810 | - } |
4811 | - // TODO(bradfitz): escape at least newlines in ruri? |
4812 | - |
4813 | - bw := bufio.NewWriter(w) |
4814 | - fmt.Fprintf(bw, "%s %s HTTP/1.1\r\n", valueOrDefault(req.Method, "GET"), ruri) |
4815 | - |
4816 | - // Header lines |
4817 | - fmt.Fprintf(bw, "Host: %s\r\n", host) |
4818 | - |
4819 | - // Use the defaultUserAgent unless the Header contains one, which |
4820 | - // may be blank to not send the header. |
4821 | - userAgent := defaultUserAgent |
4822 | - if req.Header != nil { |
4823 | - if ua := req.Header["User-Agent"]; len(ua) > 0 { |
4824 | - userAgent = ua[0] |
4825 | - } |
4826 | - } |
4827 | - if userAgent != "" { |
4828 | - fmt.Fprintf(bw, "User-Agent: %s\r\n", userAgent) |
4829 | - } |
4830 | - |
4831 | - // Process Body,ContentLength,Close,Trailer |
4832 | - tw, err := newTransferWriter(req) |
4833 | - if err != nil { |
4834 | - return err |
4835 | - } |
4836 | - err = tw.WriteHeader(bw) |
4837 | - if err != nil { |
4838 | - return err |
4839 | - } |
4840 | - |
4841 | - // TODO: split long values? (If so, should share code with Conn.Write) |
4842 | - err = req.Header.WriteSubset(bw, reqWriteExcludeHeader) |
4843 | - if err != nil { |
4844 | - return err |
4845 | - } |
4846 | - |
4847 | - if extraHeaders != nil { |
4848 | - err = extraHeaders.Write(bw) |
4849 | - if err != nil { |
4850 | - return err |
4851 | - } |
4852 | - } |
4853 | - |
4854 | - io.WriteString(bw, "\r\n") |
4855 | - |
4856 | - // Write body and trailer |
4857 | - err = tw.WriteBody(bw) |
4858 | - if err != nil { |
4859 | - return err |
4860 | - } |
4861 | - |
4862 | - return bw.Flush() |
4863 | + host := req.Host |
4864 | + if host == "" { |
4865 | + if req.URL == nil { |
4866 | + return errors.New("http: Request.Write on Request with no Host or URL set") |
4867 | + } |
4868 | + host = req.URL.Host |
4869 | + } |
4870 | + |
4871 | + ruri := req.URL.RequestURI() |
4872 | + if usingProxy && req.URL.Scheme != "" && req.URL.Opaque == "" { |
4873 | + ruri = req.URL.Scheme + "://" + host + ruri |
4874 | + } else if req.Method == "CONNECT" && req.URL.Path == "" { |
4875 | + // CONNECT requests normally give just the host and port, not a full URL. |
4876 | + ruri = host |
4877 | + } |
4878 | + // TODO(bradfitz): escape at least newlines in ruri? |
4879 | + |
4880 | + bw := bufio.NewWriter(w) |
4881 | + fmt.Fprintf(bw, "%s %s HTTP/1.1\r\n", valueOrDefault(req.Method, "GET"), ruri) |
4882 | + |
4883 | + // Header lines |
4884 | + fmt.Fprintf(bw, "Host: %s\r\n", host) |
4885 | + |
4886 | + // Use the defaultUserAgent unless the Header contains one, which |
4887 | + // may be blank to not send the header. |
4888 | + userAgent := defaultUserAgent |
4889 | + if req.Header != nil { |
4890 | + if ua := req.Header["User-Agent"]; len(ua) > 0 { |
4891 | + userAgent = ua[0] |
4892 | + } |
4893 | + } |
4894 | + if userAgent != "" { |
4895 | + fmt.Fprintf(bw, "User-Agent: %s\r\n", userAgent) |
4896 | + } |
4897 | + |
4898 | + // Process Body,ContentLength,Close,Trailer |
4899 | + tw, err := newTransferWriter(req) |
4900 | + if err != nil { |
4901 | + return err |
4902 | + } |
4903 | + err = tw.WriteHeader(bw) |
4904 | + if err != nil { |
4905 | + return err |
4906 | + } |
4907 | + |
4908 | + // TODO: split long values? (If so, should share code with Conn.Write) |
4909 | + err = req.Header.WriteSubset(bw, reqWriteExcludeHeader) |
4910 | + if err != nil { |
4911 | + return err |
4912 | + } |
4913 | + |
4914 | + if extraHeaders != nil { |
4915 | + err = extraHeaders.Write(bw) |
4916 | + if err != nil { |
4917 | + return err |
4918 | + } |
4919 | + } |
4920 | + |
4921 | + io.WriteString(bw, "\r\n") |
4922 | + |
4923 | + // Write body and trailer |
4924 | + err = tw.WriteBody(bw) |
4925 | + if err != nil { |
4926 | + return err |
4927 | + } |
4928 | + |
4929 | + return bw.Flush() |
4930 | } |
4931 | |
4932 | // Convert decimal at s[i:len(s)] to integer, |
4933 | // returning value, string position where the digits stopped, |
4934 | // and whether there was a valid number (digits, not too big). |
4935 | func atoi(s string, i int) (n, i1 int, ok bool) { |
4936 | - const Big = 1000000 |
4937 | - if i >= len(s) || s[i] < '0' || s[i] > '9' { |
4938 | - return 0, 0, false |
4939 | - } |
4940 | - n = 0 |
4941 | - for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { |
4942 | - n = n*10 + int(s[i]-'0') |
4943 | - if n > Big { |
4944 | - return 0, 0, false |
4945 | - } |
4946 | - } |
4947 | - return n, i, true |
4948 | + const Big = 1000000 |
4949 | + if i >= len(s) || s[i] < '0' || s[i] > '9' { |
4950 | + return 0, 0, false |
4951 | + } |
4952 | + n = 0 |
4953 | + for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { |
4954 | + n = n*10 + int(s[i]-'0') |
4955 | + if n > Big { |
4956 | + return 0, 0, false |
4957 | + } |
4958 | + } |
4959 | + return n, i, true |
4960 | } |
4961 | |
4962 | // ParseHTTPVersion parses a HTTP version string. |
4963 | // "HTTP/1.0" returns (1, 0, true). |
4964 | func ParseHTTPVersion(vers string) (major, minor int, ok bool) { |
4965 | - if len(vers) < 5 || vers[0:5] != "HTTP/" { |
4966 | - return 0, 0, false |
4967 | - } |
4968 | - major, i, ok := atoi(vers, 5) |
4969 | - if !ok || i >= len(vers) || vers[i] != '.' { |
4970 | - return 0, 0, false |
4971 | - } |
4972 | - minor, i, ok = atoi(vers, i+1) |
4973 | - if !ok || i != len(vers) { |
4974 | - return 0, 0, false |
4975 | - } |
4976 | - return major, minor, true |
4977 | + if len(vers) < 5 || vers[0:5] != "HTTP/" { |
4978 | + return 0, 0, false |
4979 | + } |
4980 | + major, i, ok := atoi(vers, 5) |
4981 | + if !ok || i >= len(vers) || vers[i] != '.' { |
4982 | + return 0, 0, false |
4983 | + } |
4984 | + minor, i, ok = atoi(vers, i+1) |
4985 | + if !ok || i != len(vers) { |
4986 | + return 0, 0, false |
4987 | + } |
4988 | + return major, minor, true |
4989 | } |
4990 | |
4991 | // NewRequest returns a new Request given a method, URL, and optional body. |
4992 | func NewRequest(method, urlStr string, body io.Reader) (*Request, error) { |
4993 | - u, err := url.Parse(urlStr) |
4994 | - if err != nil { |
4995 | - return nil, err |
4996 | - } |
4997 | - rc, ok := body.(io.ReadCloser) |
4998 | - if !ok && body != nil { |
4999 | - rc = ioutil.NopCloser(body) |
5000 | - } |
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.
>