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

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

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

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

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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.

Subscribers

People subscribed via source and target branches

to all changes: