Merge lp:~pedronis/ubuntu-push/update-http13 into lp:ubuntu-push
- update-http13
- Merge into trunk
Proposed by
Samuele Pedroni
Status: | Superseded |
---|---|
Proposed branch: | lp:~pedronis/ubuntu-push/update-http13 |
Merge into: | lp:ubuntu-push |
Diff against target: |
6384 lines (+2600/-1451) 48 files modified
bus/endpoint.go (+4/-4) bus/testing/testing_endpoint_test.go (+1/-1) client/client.go (+64/-27) client/client_test.go (+69/-19) client/service/common.go (+99/-0) client/service/postal.go (+122/-0) client/service/postal_test.go (+191/-0) client/service/service.go (+53/-165) client/service/service_test.go (+44/-136) client/session/session.go (+6/-17) client/session/session_test.go (+15/-28) debian/changelog (+25/-0) debian/config.json (+3/-1) debian/rules (+0/-1) http13client/Makefile (+2/-1) http13client/_patches/empty_server.patch (+118/-42) http13client/_patches/fix_code.patch (+73/-65) http13client/_patches/fix_status.patch (+33/-20) http13client/_patches/fix_tests.patch (+338/-384) http13client/_using.txt (+3/-3) http13client/client.go (+11/-3) http13client/client_test.go (+92/-0) http13client/cookie.go (+19/-37) http13client/cookie_test.go (+0/-304) http13client/request.go (+62/-50) http13client/request_test.go (+48/-8) http13client/response.go (+52/-5) http13client/response_test.go (+4/-0) http13client/responsewrite_test.go (+117/-1) http13client/server.go (+11/-5) http13client/transfer.go (+59/-27) http13client/transport.go (+114/-42) http13client/transport_test.go (+296/-17) nih/nih.go (+1/-1) scripts/dummyauth.sh (+3/-0) scripts/register (+43/-0) scripts/unicast (+2/-0) server/acceptance/acceptanceclient.go (+1/-1) server/acceptance/cmd/acceptanceclient.go (+3/-1) server/acceptance/suites/suite.go (+2/-2) server/acceptance/suites/unicast.go (+11/-3) server/api/handlers.go (+104/-8) server/api/handlers_test.go (+193/-2) server/store/inmemory.go (+26/-0) server/store/inmemory_test.go (+44/-0) server/store/store.go (+8/-0) signing-helper/signing-helper.cpp (+6/-10) whoopsie/identifier/identifier.go (+5/-10) |
To merge this branch: | bzr merge lp:~pedronis/ubuntu-push/update-http13 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Push Hackers | Pending | ||
Review via email: mp+223915@code.launchpad.net |
Commit message
update http13client from the actual go1.3 release
Description of the change
update http13client from the actual go1.3 release
To post a comment you must log in.
- 187. By Samuele Pedroni
-
changelog
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'bus/endpoint.go' |
2 | --- bus/endpoint.go 2014-05-15 11:28:06 +0000 |
3 | +++ bus/endpoint.go 2014-06-20 13:02:58 +0000 |
4 | @@ -29,7 +29,7 @@ |
5 | * Endpoint (and its implementation) |
6 | */ |
7 | |
8 | -type BusMethod func([]interface{}, []interface{}) ([]interface{}, error) |
9 | +type BusMethod func(string, []interface{}, []interface{}) ([]interface{}, error) |
10 | type DispatchMap map[string]BusMethod |
11 | |
12 | // bus.Endpoint represents the DBus connection itself. |
13 | @@ -249,12 +249,12 @@ |
14 | endp.log.Errorf("WatchMethod: unknown method %s", msg.Member) |
15 | } else { |
16 | args := msg.AllArgs() |
17 | - rvals, err := meth(args, extra) |
18 | + rvals, err := meth(string(msg.Path), args, extra) |
19 | if err != nil { |
20 | reply = dbus.NewErrorMessage(msg, err_iface, err.Error()) |
21 | - endp.log.Errorf("WatchMethod: %s(%#v, %#v) failure: %#v", msg.Member, args, extra, err) |
22 | + endp.log.Errorf("WatchMethod: %s(%v, %#v, %#v) failure: %#v", msg.Member, msg.Path, args, extra, err) |
23 | } else { |
24 | - endp.log.Debugf("WatchMethod: %s(%#v, %#v) success: %#v", msg.Member, args, extra, rvals) |
25 | + endp.log.Debugf("WatchMethod: %s(%v, %#v, %#v) success: %#v", msg.Member, msg.Path, args, extra, rvals) |
26 | reply = dbus.NewMethodReturnMessage(msg) |
27 | err = reply.AppendArgs(rvals...) |
28 | if err != nil { |
29 | |
30 | === modified file 'bus/testing/testing_endpoint_test.go' |
31 | --- bus/testing/testing_endpoint_test.go 2014-06-02 10:04:34 +0000 |
32 | +++ bus/testing/testing_endpoint_test.go 2014-06-20 13:02:58 +0000 |
33 | @@ -229,7 +229,7 @@ |
34 | // Test that WatchMethod updates callArgs |
35 | func (s *TestingEndpointSuite) TestWatchMethodUpdatesCallArgs(c *C) { |
36 | endp := NewTestingEndpoint(nil, condition.Work(true)) |
37 | - foo := func([]interface{}, []interface{}) ([]interface{}, error) { return nil, nil } |
38 | + foo := func(string, []interface{}, []interface{}) ([]interface{}, error) { return nil, nil } |
39 | foomp := bus.DispatchMap{"foo": foo} |
40 | endp.WatchMethod(foomp) |
41 | c.Check(GetCallArgs(endp), DeepEquals, []callArgs{ |
42 | |
43 | === modified file 'client/client.go' |
44 | --- client/client.go 2014-05-21 10:03:18 +0000 |
45 | +++ client/client.go 2014-06-20 13:02:58 +0000 |
46 | @@ -28,6 +28,7 @@ |
47 | "fmt" |
48 | "io/ioutil" |
49 | "os" |
50 | + "os/exec" |
51 | "strings" |
52 | |
53 | "launchpad.net/go-dbus/v1" |
54 | @@ -63,32 +64,36 @@ |
55 | // The PEM-encoded server certificate |
56 | CertPEMFile string `json:"cert_pem_file"` |
57 | // How to invoke the auth helper |
58 | - AuthHelper []string `json:"auth_helper"` |
59 | + AuthHelper string `json:"auth_helper"` |
60 | + SessionURL string `json:"session_url"` |
61 | + RegistrationURL string `json:"registration_url"` |
62 | // The logging level (one of "debug", "info", "error") |
63 | LogLevel logger.ConfigLogLevel `json:"log_level"` |
64 | } |
65 | |
66 | // PushClient is the Ubuntu Push Notifications client-side daemon. |
67 | type PushClient struct { |
68 | - leveldbPath string |
69 | - configPath string |
70 | - config ClientConfig |
71 | - log logger.Logger |
72 | - pem []byte |
73 | - idder identifier.Id |
74 | - deviceId string |
75 | - notificationsEndp bus.Endpoint |
76 | - urlDispatcherEndp bus.Endpoint |
77 | - connectivityEndp bus.Endpoint |
78 | - systemImageEndp bus.Endpoint |
79 | - systemImageInfo *systemimage.InfoResult |
80 | - connCh chan bool |
81 | - hasConnectivity bool |
82 | - actionsCh <-chan notifications.RawActionReply |
83 | - session *session.ClientSession |
84 | - sessionConnectedCh chan uint32 |
85 | - serviceEndpoint bus.Endpoint |
86 | - service *service.Service |
87 | + leveldbPath string |
88 | + configPath string |
89 | + config ClientConfig |
90 | + log logger.Logger |
91 | + pem []byte |
92 | + idder identifier.Id |
93 | + deviceId string |
94 | + notificationsEndp bus.Endpoint |
95 | + urlDispatcherEndp bus.Endpoint |
96 | + connectivityEndp bus.Endpoint |
97 | + systemImageEndp bus.Endpoint |
98 | + systemImageInfo *systemimage.InfoResult |
99 | + connCh chan bool |
100 | + hasConnectivity bool |
101 | + actionsCh <-chan notifications.RawActionReply |
102 | + session *session.ClientSession |
103 | + sessionConnectedCh chan uint32 |
104 | + pushServiceEndpoint bus.Endpoint |
105 | + pushService *service.PushService |
106 | + postalServiceEndpoint bus.Endpoint |
107 | + postalService *service.PostalService |
108 | } |
109 | |
110 | var ( |
111 | @@ -160,7 +165,27 @@ |
112 | ExpectAllRepairedTime: client.config.ExpectAllRepairedTime.TimeDuration(), |
113 | PEM: client.pem, |
114 | Info: info, |
115 | - AuthHelper: client.config.AuthHelper, |
116 | + AuthGetter: client.getAuthorization, |
117 | + AuthURL: client.config.SessionURL, |
118 | + } |
119 | +} |
120 | + |
121 | +// getAuthorization gets the authorization blob to send to the server |
122 | +func (client *PushClient) getAuthorization(url string) string { |
123 | + client.log.Debugf("getting authorization for %s", url) |
124 | + // using a helper, for now at least |
125 | + if len(client.config.AuthHelper) == 0 { |
126 | + // do nothing if helper is unset or empty |
127 | + return "" |
128 | + } |
129 | + |
130 | + auth, err := exec.Command(client.config.AuthHelper, url).Output() |
131 | + if err != nil { |
132 | + // For now we just log the error, as we don't want to block unauthorized users |
133 | + client.log.Errorf("unable to get the authorization token from the account: %v", err) |
134 | + return "" |
135 | + } else { |
136 | + return strings.TrimSpace(string(auth)) |
137 | } |
138 | } |
139 | |
140 | @@ -331,7 +356,7 @@ |
141 | // handleUnicastNotification deals with receiving a unicast notification |
142 | func (client *PushClient) handleUnicastNotification(msg *protocol.Notification) error { |
143 | client.log.Debugf("sending notification %#v for %#v.", msg.MsgId, msg.AppId) |
144 | - return client.service.Inject(msg.AppId, string(msg.Payload)) |
145 | + return client.postalService.Inject(msg.AppId, string(msg.Payload)) |
146 | } |
147 | |
148 | // handleClick deals with the user clicking a notification |
149 | @@ -422,13 +447,25 @@ |
150 | } |
151 | |
152 | func (client *PushClient) startService() error { |
153 | - if client.serviceEndpoint == nil { |
154 | - client.serviceEndpoint = bus.SessionBus.Endpoint(service.BusAddress, client.log) |
155 | + if client.pushServiceEndpoint == nil { |
156 | + client.pushServiceEndpoint = bus.SessionBus.Endpoint(service.PushServiceBusAddress, client.log) |
157 | + } |
158 | + if client.postalServiceEndpoint == nil { |
159 | + client.postalServiceEndpoint = bus.SessionBus.Endpoint(service.PostalServiceBusAddress, client.log) |
160 | } |
161 | |
162 | - client.service = service.NewService(client.serviceEndpoint, client.log) |
163 | - client.service.SetMessageHandler(client.messageHandler) |
164 | - return client.service.Start() |
165 | + client.pushService = service.NewPushService(client.pushServiceEndpoint, client.log) |
166 | + client.pushService.SetRegistrationURL(client.config.RegistrationURL) |
167 | + client.pushService.SetAuthGetter(client.getAuthorization) |
168 | + client.postalService = service.NewPostalService(client.postalServiceEndpoint, client.log) |
169 | + client.postalService.SetMessageHandler(client.messageHandler) |
170 | + if err := client.pushService.Start(); err != nil { |
171 | + return err |
172 | + } |
173 | + if err := client.postalService.Start(); err != nil { |
174 | + return err |
175 | + } |
176 | + return nil |
177 | } |
178 | |
179 | // Start calls doStart with the "real" starters |
180 | |
181 | === modified file 'client/client_test.go' |
182 | --- client/client_test.go 2014-05-20 13:56:37 +0000 |
183 | +++ client/client_test.go 2014-06-20 13:02:58 +0000 |
184 | @@ -103,11 +103,13 @@ |
185 | "stabilizing_timeout": "0ms", |
186 | "connectivity_check_url": "", |
187 | "connectivity_check_md5": "", |
188 | - "addr": ":0", |
189 | - "cert_pem_file": pem_file, |
190 | - "recheck_timeout": "3h", |
191 | - "auth_helper": []string{}, |
192 | - "log_level": "debug", |
193 | + "addr": ":0", |
194 | + "cert_pem_file": pem_file, |
195 | + "recheck_timeout": "3h", |
196 | + "auth_helper": "", |
197 | + "session_url": "xyzzy://", |
198 | + "registration_url": "reg://", |
199 | + "log_level": "debug", |
200 | } |
201 | for k, v := range overrides { |
202 | cfgMap[k] = v |
203 | @@ -257,7 +259,7 @@ |
204 | |
205 | func (cs *clientSuite) TestDeriveSessionConfig(c *C) { |
206 | cs.writeTestConfig(map[string]interface{}{ |
207 | - "auth_helper": []string{"auth", "helper"}, |
208 | + "auth_helper": "auth helper", |
209 | }) |
210 | info := map[string]interface{}{ |
211 | "foo": 1, |
212 | @@ -272,7 +274,8 @@ |
213 | ExpectAllRepairedTime: 30 * time.Minute, |
214 | PEM: cli.pem, |
215 | Info: info, |
216 | - AuthHelper: []string{"auth", "helper"}, |
217 | + AuthGetter: func(string) string { return "" }, |
218 | + AuthURL: "xyzzy://", |
219 | } |
220 | // sanity check that we are looking at all fields |
221 | vExpected := reflect.ValueOf(expected) |
222 | @@ -284,6 +287,11 @@ |
223 | } |
224 | // finally compare |
225 | conf := cli.deriveSessionConfig(info) |
226 | + // compare authGetter by string |
227 | + c.Check(fmt.Sprintf("%v", conf.AuthGetter), Equals, fmt.Sprintf("%v", cli.getAuthorization)) |
228 | + // and set it to nil |
229 | + conf.AuthGetter = nil |
230 | + expected.AuthGetter = nil |
231 | c.Check(conf, DeepEquals, expected) |
232 | } |
233 | |
234 | @@ -292,15 +300,23 @@ |
235 | ******************************************************************/ |
236 | |
237 | func (cs *clientSuite) TestStartServiceWorks(c *C) { |
238 | + cs.writeTestConfig(map[string]interface{}{ |
239 | + "auth_helper": "../scripts/dummyauth.sh", |
240 | + }) |
241 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
242 | + cli.configure() |
243 | cli.log = cs.log |
244 | - cli.serviceEndpoint = testibus.NewTestingEndpoint(condition.Work(true), nil) |
245 | - c.Check(cli.service, IsNil) |
246 | + cli.pushServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(true), nil) |
247 | + cli.postalServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(true), nil) |
248 | + c.Check(cli.pushService, IsNil) |
249 | c.Check(cli.startService(), IsNil) |
250 | - c.Assert(cli.service, NotNil) |
251 | - c.Check(cli.service.IsRunning(), Equals, true) |
252 | - c.Check(cli.service.GetMessageHandler(), NotNil) |
253 | - cli.service.Stop() |
254 | + c.Assert(cli.pushService, NotNil) |
255 | + c.Check(cli.pushService.IsRunning(), Equals, true) |
256 | + c.Check(cli.postalService.IsRunning(), Equals, true) |
257 | + c.Check(cli.postalService.GetMessageHandler(), NotNil) |
258 | + c.Check(cli.pushService.GetRegistrationAuthorization(), Equals, "hello reg://") |
259 | + cli.pushService.Stop() |
260 | + cli.postalService.Stop() |
261 | } |
262 | |
263 | func (cs *clientSuite) TestStartServiceErrorsOnNilLog(c *C) { |
264 | @@ -312,7 +328,8 @@ |
265 | func (cs *clientSuite) TestStartServiceErrorsOnBusDialFail(c *C) { |
266 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
267 | cli.log = cs.log |
268 | - cli.serviceEndpoint = testibus.NewTestingEndpoint(condition.Work(false), nil) |
269 | + cli.pushServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(false), nil) |
270 | + cli.postalServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(false), nil) |
271 | c.Check(cli.startService(), NotNil) |
272 | } |
273 | |
274 | @@ -647,14 +664,16 @@ |
275 | func (cs *clientSuite) TestHandleUcastNotification(c *C) { |
276 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
277 | svcEndp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1)) |
278 | + postEndp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1)) |
279 | cli.log = cs.log |
280 | - cli.serviceEndpoint = svcEndp |
281 | + cli.pushServiceEndpoint = svcEndp |
282 | + cli.postalServiceEndpoint = postEndp |
283 | notsEndp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
284 | cli.notificationsEndp = notsEndp |
285 | c.Assert(cli.startService(), IsNil) |
286 | c.Check(cli.handleUnicastNotification(notif), IsNil) |
287 | // check we sent the notification |
288 | - args := testibus.GetCallArgs(svcEndp) |
289 | + args := testibus.GetCallArgs(postEndp) |
290 | c.Assert(len(args), Not(Equals), 0) |
291 | c.Check(args[len(args)-1].Member, Equals, "::Signal") |
292 | c.Check(cs.log.Captured(), Matches, `(?m).*sending notification "42" for "hello".*`) |
293 | @@ -879,7 +898,7 @@ |
294 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
295 | // before start, everything sucks: |
296 | // no service, |
297 | - c.Check(cli.service, IsNil) |
298 | + c.Check(cli.pushService, IsNil) |
299 | // no config, |
300 | c.Check(string(cli.config.Addr), Equals, "") |
301 | // no device id, |
302 | @@ -904,9 +923,10 @@ |
303 | // and a bus, |
304 | c.Check(cli.notificationsEndp, NotNil) |
305 | // and a service, |
306 | - c.Check(cli.service, NotNil) |
307 | + c.Check(cli.pushService, NotNil) |
308 | // and everthying us just peachy! |
309 | - cli.service.Stop() // cleanup |
310 | + cli.pushService.Stop() // cleanup |
311 | + cli.postalService.Stop() // cleanup |
312 | } |
313 | |
314 | func (cs *clientSuite) TestStartCanFail(c *C) { |
315 | @@ -951,3 +971,33 @@ |
316 | c.Assert(err, NotNil) |
317 | c.Check(cs.log.Captured(), Matches, "(?msi).*showing notification: no way$") |
318 | } |
319 | + |
320 | +/***************************************************************** |
321 | + getAuthorization() tests |
322 | +******************************************************************/ |
323 | + |
324 | +func (cs *clientSuite) TestGetAuthorizationIgnoresErrors(c *C) { |
325 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
326 | + cli.configure() |
327 | + cli.config.AuthHelper = "/no/such/executable" |
328 | + |
329 | + c.Check(cli.getAuthorization("xyzzy://"), Equals, "") |
330 | +} |
331 | + |
332 | +func (cs *clientSuite) TestGetAuthorizationGetsIt(c *C) { |
333 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
334 | + cli.configure() |
335 | + cli.config.AuthHelper = "../scripts/dummyauth.sh" |
336 | + |
337 | + c.Check(cli.getAuthorization("xyzzy://"), Equals, "hello xyzzy://") |
338 | +} |
339 | + |
340 | +func (cs *clientSuite) TestGetAuthorizationWorksIfUnsetOrNil(c *C) { |
341 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
342 | + cli.log = cs.log |
343 | + |
344 | + c.Assert(cli.config, NotNil) |
345 | + c.Check(cli.getAuthorization("xyzzy://"), Equals, "") |
346 | + cli.configure() |
347 | + c.Check(cli.getAuthorization("xyzzy://"), Equals, "") |
348 | +} |
349 | |
350 | === added file 'client/service/common.go' |
351 | --- client/service/common.go 1970-01-01 00:00:00 +0000 |
352 | +++ client/service/common.go 2014-06-20 13:02:58 +0000 |
353 | @@ -0,0 +1,99 @@ |
354 | +/* |
355 | + Copyright 2014 Canonical Ltd. |
356 | + |
357 | + This program is free software: you can redistribute it and/or modify it |
358 | + under the terms of the GNU General Public License version 3, as published |
359 | + by the Free Software Foundation. |
360 | + |
361 | + This program is distributed in the hope that it will be useful, but |
362 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
363 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
364 | + PURPOSE. See the GNU General Public License for more details. |
365 | + |
366 | + You should have received a copy of the GNU General Public License along |
367 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
368 | +*/ |
369 | + |
370 | +// package service implements the dbus-level service with which client |
371 | +// applications are expected to interact. |
372 | +package service |
373 | + |
374 | +import ( |
375 | + "errors" |
376 | + "sync" |
377 | + |
378 | + "launchpad.net/ubuntu-push/bus" |
379 | + "launchpad.net/ubuntu-push/logger" |
380 | +) |
381 | + |
382 | +type DBusService struct { |
383 | + lock sync.RWMutex |
384 | + state ServiceState |
385 | + Log logger.Logger |
386 | + Bus bus.Endpoint |
387 | +} |
388 | + |
389 | +// the service can be in a numnber of states |
390 | +type ServiceState uint8 |
391 | + |
392 | +const ( |
393 | + StateUnknown ServiceState = iota |
394 | + StateRunning // Start() has been successfully called |
395 | + StateFinished // Stop() has been successfully called |
396 | +) |
397 | + |
398 | +var ( |
399 | + NotConfigured = errors.New("not configured") |
400 | + AlreadyStarted = errors.New("already started") |
401 | + BadArgCount = errors.New("Wrong number of arguments") |
402 | + BadArgType = errors.New("Bad argument type") |
403 | +) |
404 | + |
405 | +// IsRunning() returns whether the service's state is StateRunning |
406 | +func (svc *DBusService) IsRunning() bool { |
407 | + svc.lock.RLock() |
408 | + defer svc.lock.RUnlock() |
409 | + return svc.state == StateRunning |
410 | +} |
411 | + |
412 | +// Start() dials the bus, grab the name, and listens for method calls. |
413 | +func (svc *DBusService) Start(dispatchMap bus.DispatchMap, busAddr bus.Address) error { |
414 | + svc.lock.Lock() |
415 | + defer svc.lock.Unlock() |
416 | + if svc.state != StateUnknown { |
417 | + return AlreadyStarted |
418 | + } |
419 | + if svc.Log == nil || svc.Bus == nil { |
420 | + return NotConfigured |
421 | + } |
422 | + err := svc.Bus.Dial() |
423 | + if err != nil { |
424 | + return err |
425 | + } |
426 | + ch := svc.Bus.GrabName(true) |
427 | + log := svc.Log |
428 | + go func() { |
429 | + for err := range ch { |
430 | + if !svc.IsRunning() { |
431 | + break |
432 | + } |
433 | + if err != nil { |
434 | + log.Fatalf("name channel for %s got: %v", |
435 | + busAddr.Name, err) |
436 | + } |
437 | + } |
438 | + }() |
439 | + svc.Bus.WatchMethod(dispatchMap, svc) |
440 | + svc.state = StateRunning |
441 | + return nil |
442 | +} |
443 | + |
444 | +// Stop() closes the bus and sets the state to StateFinished |
445 | +func (svc *DBusService) Stop() { |
446 | + svc.lock.Lock() |
447 | + defer svc.lock.Unlock() |
448 | + if svc.Bus != nil { |
449 | + svc.Bus.Close() |
450 | + } |
451 | + svc.state = StateFinished |
452 | +} |
453 | |
454 | === added file 'client/service/postal.go' |
455 | --- client/service/postal.go 1970-01-01 00:00:00 +0000 |
456 | +++ client/service/postal.go 2014-06-20 13:02:58 +0000 |
457 | @@ -0,0 +1,122 @@ |
458 | +/* |
459 | + Copyright 2013-2014 Canonical Ltd. |
460 | + |
461 | + This program is free software: you can redistribute it and/or modify it |
462 | + under the terms of the GNU General Public License version 3, as published |
463 | + by the Free Software Foundation. |
464 | + |
465 | + This program is distributed in the hope that it will be useful, but |
466 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
467 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
468 | + PURPOSE. See the GNU General Public License for more details. |
469 | + |
470 | + You should have received a copy of the GNU General Public License along |
471 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
472 | +*/ |
473 | + |
474 | +package service |
475 | + |
476 | +import ( |
477 | + "strings" |
478 | + |
479 | + "launchpad.net/ubuntu-push/bus" |
480 | + "launchpad.net/ubuntu-push/logger" |
481 | + "launchpad.net/ubuntu-push/nih" |
482 | +) |
483 | + |
484 | +// PostalService is the dbus api |
485 | +type PostalService struct { |
486 | + DBusService |
487 | + mbox map[string][]string |
488 | + msgHandler func([]byte) error |
489 | +} |
490 | + |
491 | +var ( |
492 | + PostalServiceBusAddress = bus.Address{ |
493 | + Interface: "com.ubuntu.Postal", |
494 | + Path: "/com/ubuntu/Postal/*", |
495 | + Name: "com.ubuntu.Postal", |
496 | + } |
497 | +) |
498 | + |
499 | +// NewPostalService() builds a new service and returns it. |
500 | +func NewPostalService(bus bus.Endpoint, log logger.Logger) *PostalService { |
501 | + var svc = &PostalService{} |
502 | + svc.Log = log |
503 | + svc.Bus = bus |
504 | + return svc |
505 | +} |
506 | + |
507 | +// SetMessageHandler() sets the message-handling callback |
508 | +func (svc *PostalService) SetMessageHandler(callback func([]byte) error) { |
509 | + svc.lock.RLock() |
510 | + defer svc.lock.RUnlock() |
511 | + svc.msgHandler = callback |
512 | +} |
513 | + |
514 | +// GetMessageHandler() returns the (possibly nil) messaging handler callback |
515 | +func (svc *PostalService) GetMessageHandler() func([]byte) error { |
516 | + svc.lock.RLock() |
517 | + defer svc.lock.RUnlock() |
518 | + return svc.msgHandler |
519 | +} |
520 | + |
521 | +// Start() dials the bus, grab the name, and listens for method calls. |
522 | +func (svc *PostalService) Start() error { |
523 | + return svc.DBusService.Start(bus.DispatchMap{ |
524 | + "Notifications": svc.notifications, |
525 | + "Inject": svc.inject, |
526 | + }, PostalServiceBusAddress) |
527 | +} |
528 | + |
529 | +func (svc *PostalService) notifications(path string, args, _ []interface{}) ([]interface{}, error) { |
530 | + if len(args) != 0 { |
531 | + return nil, BadArgCount |
532 | + } |
533 | + appname := string(nih.Unquote([]byte(path[strings.LastIndex(path, "/")+1:]))) |
534 | + |
535 | + svc.lock.Lock() |
536 | + defer svc.lock.Unlock() |
537 | + |
538 | + if svc.mbox == nil { |
539 | + return []interface{}{[]string(nil)}, nil |
540 | + } |
541 | + msgs := svc.mbox[appname] |
542 | + delete(svc.mbox, appname) |
543 | + |
544 | + return []interface{}{msgs}, nil |
545 | +} |
546 | + |
547 | +func (svc *PostalService) inject(path string, args, _ []interface{}) ([]interface{}, error) { |
548 | + if len(args) != 1 { |
549 | + return nil, BadArgCount |
550 | + } |
551 | + notif, ok := args[0].(string) |
552 | + if !ok { |
553 | + return nil, BadArgType |
554 | + } |
555 | + appname := string(nih.Unquote([]byte(path[strings.LastIndex(path, "/")+1:]))) |
556 | + |
557 | + return nil, svc.Inject(appname, notif) |
558 | +} |
559 | + |
560 | +// Inject() signals to an application over dbus that a notification |
561 | +// has arrived. |
562 | +func (svc *PostalService) Inject(appname string, notif string) error { |
563 | + svc.lock.Lock() |
564 | + defer svc.lock.Unlock() |
565 | + if svc.mbox == nil { |
566 | + svc.mbox = make(map[string][]string) |
567 | + } |
568 | + svc.mbox[appname] = append(svc.mbox[appname], notif) |
569 | + if svc.msgHandler != nil { |
570 | + err := svc.msgHandler([]byte(notif)) |
571 | + if err != nil { |
572 | + svc.DBusService.Log.Errorf("msgHandler returned %v", err) |
573 | + return err |
574 | + } |
575 | + svc.DBusService.Log.Debugf("call to msgHandler successful") |
576 | + } |
577 | + |
578 | + return svc.Bus.Signal("Notification", []interface{}{appname}) |
579 | +} |
580 | |
581 | === added file 'client/service/postal_test.go' |
582 | --- client/service/postal_test.go 1970-01-01 00:00:00 +0000 |
583 | +++ client/service/postal_test.go 2014-06-20 13:02:58 +0000 |
584 | @@ -0,0 +1,191 @@ |
585 | +/* |
586 | + Copyright 2014 Canonical Ltd. |
587 | + |
588 | + This program is free software: you can redistribute it and/or modify it |
589 | + under the terms of the GNU General Public License version 3, as published |
590 | + by the Free Software Foundation. |
591 | + |
592 | + This program is distributed in the hope that it will be useful, but |
593 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
594 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
595 | + PURPOSE. See the GNU General Public License for more details. |
596 | + |
597 | + You should have received a copy of the GNU General Public License along |
598 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
599 | +*/ |
600 | + |
601 | +package service |
602 | + |
603 | +import ( |
604 | + "errors" |
605 | + |
606 | + . "launchpad.net/gocheck" |
607 | + |
608 | + "launchpad.net/ubuntu-push/bus" |
609 | + testibus "launchpad.net/ubuntu-push/bus/testing" |
610 | + "launchpad.net/ubuntu-push/logger" |
611 | + helpers "launchpad.net/ubuntu-push/testing" |
612 | + "launchpad.net/ubuntu-push/testing/condition" |
613 | +) |
614 | + |
615 | +type postalSuite struct { |
616 | + log logger.Logger |
617 | + bus bus.Endpoint |
618 | +} |
619 | + |
620 | +var _ = Suite(&postalSuite{}) |
621 | + |
622 | +func (ss *postalSuite) SetUpTest(c *C) { |
623 | + ss.log = helpers.NewTestLogger(c, "debug") |
624 | + ss.bus = testibus.NewTestingEndpoint(condition.Work(true), nil) |
625 | +} |
626 | + |
627 | +func (ss *postalSuite) TestStart(c *C) { |
628 | + svc := NewPostalService(ss.bus, ss.log) |
629 | + c.Check(svc.IsRunning(), Equals, false) |
630 | + c.Check(svc.Start(), IsNil) |
631 | + c.Check(svc.IsRunning(), Equals, true) |
632 | + svc.Stop() |
633 | +} |
634 | + |
635 | +func (ss *postalSuite) TestStartTwice(c *C) { |
636 | + svc := NewPostalService(ss.bus, ss.log) |
637 | + c.Check(svc.Start(), IsNil) |
638 | + c.Check(svc.Start(), Equals, AlreadyStarted) |
639 | + svc.Stop() |
640 | +} |
641 | + |
642 | +func (ss *postalSuite) TestStartNoLog(c *C) { |
643 | + svc := NewPostalService(ss.bus, nil) |
644 | + c.Check(svc.Start(), Equals, NotConfigured) |
645 | +} |
646 | + |
647 | +func (ss *postalSuite) TestStartNoBus(c *C) { |
648 | + svc := NewPostalService(nil, ss.log) |
649 | + c.Check(svc.Start(), Equals, NotConfigured) |
650 | +} |
651 | + |
652 | +func (ss *postalSuite) TestStartFailsOnBusDialFailure(c *C) { |
653 | + bus := testibus.NewTestingEndpoint(condition.Work(false), nil) |
654 | + svc := NewPostalService(bus, ss.log) |
655 | + c.Check(svc.Start(), ErrorMatches, `.*(?i)cond said no.*`) |
656 | + svc.Stop() |
657 | +} |
658 | + |
659 | +func (ss *postalSuite) TestStartGrabsName(c *C) { |
660 | + svc := NewPostalService(ss.bus, ss.log) |
661 | + c.Assert(svc.Start(), IsNil) |
662 | + callArgs := testibus.GetCallArgs(ss.bus) |
663 | + defer svc.Stop() |
664 | + c.Assert(callArgs, NotNil) |
665 | + c.Check(callArgs[0].Member, Equals, "::GrabName") |
666 | +} |
667 | + |
668 | +func (ss *postalSuite) TestStopClosesBus(c *C) { |
669 | + svc := NewPostalService(ss.bus, ss.log) |
670 | + c.Assert(svc.Start(), IsNil) |
671 | + svc.Stop() |
672 | + callArgs := testibus.GetCallArgs(ss.bus) |
673 | + c.Assert(callArgs, NotNil) |
674 | + c.Check(callArgs[len(callArgs)-1].Member, Equals, "::Close") |
675 | +} |
676 | + |
677 | +// |
678 | +// Injection tests |
679 | + |
680 | +func (ss *postalSuite) TestInjectWorks(c *C) { |
681 | + svc := NewPostalService(ss.bus, ss.log) |
682 | + rvs, err := svc.inject("/hello", []interface{}{"world"}, nil) |
683 | + c.Assert(err, IsNil) |
684 | + c.Check(rvs, IsNil) |
685 | + rvs, err = svc.inject("/hello", []interface{}{"there"}, nil) |
686 | + c.Assert(err, IsNil) |
687 | + c.Check(rvs, IsNil) |
688 | + c.Assert(svc.mbox, HasLen, 1) |
689 | + c.Assert(svc.mbox["hello"], HasLen, 2) |
690 | + c.Check(svc.mbox["hello"][0], Equals, "world") |
691 | + c.Check(svc.mbox["hello"][1], Equals, "there") |
692 | + |
693 | + // and check it fired the right signal (twice) |
694 | + callArgs := testibus.GetCallArgs(ss.bus) |
695 | + c.Assert(callArgs, HasLen, 2) |
696 | + c.Check(callArgs[0].Member, Equals, "::Signal") |
697 | + c.Check(callArgs[0].Args, DeepEquals, []interface{}{"Notification", []interface{}{"hello"}}) |
698 | + c.Check(callArgs[1], DeepEquals, callArgs[0]) |
699 | +} |
700 | + |
701 | +func (ss *postalSuite) TestInjectFailsIfInjectFails(c *C) { |
702 | + bus := testibus.NewTestingEndpoint(condition.Work(true), |
703 | + condition.Work(false)) |
704 | + svc := NewPostalService(bus, ss.log) |
705 | + svc.SetMessageHandler(func([]byte) error { return errors.New("fail") }) |
706 | + _, err := svc.inject("/hello", []interface{}{"xyzzy"}, nil) |
707 | + c.Check(err, NotNil) |
708 | +} |
709 | + |
710 | +func (ss *postalSuite) TestInjectFailsIfBadArgs(c *C) { |
711 | + for i, s := range []struct { |
712 | + args []interface{} |
713 | + errt error |
714 | + }{ |
715 | + {nil, BadArgCount}, |
716 | + {[]interface{}{}, BadArgCount}, |
717 | + {[]interface{}{1}, BadArgType}, |
718 | + {[]interface{}{1, 2}, BadArgCount}, |
719 | + } { |
720 | + reg, err := new(PostalService).inject("", s.args, nil) |
721 | + c.Check(reg, IsNil, Commentf("iteration #%d", i)) |
722 | + c.Check(err, Equals, s.errt, Commentf("iteration #%d", i)) |
723 | + } |
724 | +} |
725 | + |
726 | +// |
727 | +// Notifications tests |
728 | +func (ss *postalSuite) TestNotificationsWorks(c *C) { |
729 | + svc := NewPostalService(ss.bus, ss.log) |
730 | + nots, err := svc.notifications("/hello", nil, nil) |
731 | + c.Assert(err, IsNil) |
732 | + c.Assert(nots, NotNil) |
733 | + c.Assert(nots, HasLen, 1) |
734 | + c.Check(nots[0], HasLen, 0) |
735 | + if svc.mbox == nil { |
736 | + svc.mbox = make(map[string][]string) |
737 | + } |
738 | + svc.mbox["hello"] = append(svc.mbox["hello"], "this", "thing") |
739 | + nots, err = svc.notifications("/hello", nil, nil) |
740 | + c.Assert(err, IsNil) |
741 | + c.Assert(nots, NotNil) |
742 | + c.Assert(nots, HasLen, 1) |
743 | + c.Check(nots[0], DeepEquals, []string{"this", "thing"}) |
744 | +} |
745 | + |
746 | +func (ss *postalSuite) TestNotificationsFailsIfBadArgs(c *C) { |
747 | + reg, err := new(PostalService).notifications("/foo", []interface{}{1}, nil) |
748 | + c.Check(reg, IsNil) |
749 | + c.Check(err, Equals, BadArgCount) |
750 | +} |
751 | + |
752 | +func (ss *postalSuite) TestMessageHandler(c *C) { |
753 | + svc := new(PostalService) |
754 | + c.Assert(svc.msgHandler, IsNil) |
755 | + var ext = []byte{} |
756 | + e := errors.New("Hello") |
757 | + f := func(s []byte) error { ext = s; return e } |
758 | + c.Check(svc.GetMessageHandler(), IsNil) |
759 | + svc.SetMessageHandler(f) |
760 | + c.Check(svc.GetMessageHandler(), NotNil) |
761 | + c.Check(svc.msgHandler([]byte("37")), Equals, e) |
762 | + c.Check(ext, DeepEquals, []byte("37")) |
763 | +} |
764 | + |
765 | +func (ss *postalSuite) TestInjectCallsMessageHandler(c *C) { |
766 | + var ext = []byte{} |
767 | + svc := NewPostalService(ss.bus, ss.log) |
768 | + f := func(s []byte) error { ext = s; return nil } |
769 | + svc.SetMessageHandler(f) |
770 | + c.Check(svc.Inject("stuff", "{}"), IsNil) |
771 | + c.Check(ext, DeepEquals, []byte("{}")) |
772 | + err := errors.New("ouch") |
773 | + svc.SetMessageHandler(func([]byte) error { return err }) |
774 | + c.Check(svc.Inject("stuff", "{}"), Equals, err) |
775 | +} |
776 | |
777 | === modified file 'client/service/service.go' |
778 | --- client/service/service.go 2014-05-21 10:03:18 +0000 |
779 | +++ client/service/service.go 2014-06-20 13:02:58 +0000 |
780 | @@ -14,196 +14,84 @@ |
781 | with this program. If not, see <http://www.gnu.org/licenses/>. |
782 | */ |
783 | |
784 | -// package service implements the dbus-level service with which client |
785 | -// applications are expected to interact. |
786 | package service |
787 | |
788 | import ( |
789 | - "errors" |
790 | "os" |
791 | - "sync" |
792 | + "strings" |
793 | |
794 | "launchpad.net/ubuntu-push/bus" |
795 | "launchpad.net/ubuntu-push/logger" |
796 | + "launchpad.net/ubuntu-push/nih" |
797 | ) |
798 | |
799 | -// Service is the dbus api |
800 | -type Service struct { |
801 | - lock sync.RWMutex |
802 | - state ServiceState |
803 | - mbox map[string][]string |
804 | - msgHandler func([]byte) error |
805 | - Log logger.Logger |
806 | - Bus bus.Endpoint |
807 | +// PushService is the dbus api |
808 | +type PushService struct { |
809 | + DBusService |
810 | + regURL string |
811 | + authGetter func(string) string |
812 | } |
813 | |
814 | -// the service can be in a numnber of states |
815 | -type ServiceState uint8 |
816 | - |
817 | -const ( |
818 | - StateUnknown ServiceState = iota |
819 | - StateRunning // Start() has been successfully called |
820 | - StateFinished // Stop() has been successfully called |
821 | -) |
822 | - |
823 | var ( |
824 | - NotConfigured = errors.New("not configured") |
825 | - AlreadyStarted = errors.New("already started") |
826 | - BusAddress = bus.Address{ |
827 | + PushServiceBusAddress = bus.Address{ |
828 | Interface: "com.ubuntu.PushNotifications", |
829 | - Path: "/com/ubuntu/PushNotifications", |
830 | + Path: "/com/ubuntu/PushNotifications/*", |
831 | Name: "com.ubuntu.PushNotifications", |
832 | } |
833 | ) |
834 | |
835 | -// NewService() builds a new service and returns it. |
836 | -func NewService(bus bus.Endpoint, log logger.Logger) *Service { |
837 | - return &Service{Log: log, Bus: bus} |
838 | -} |
839 | - |
840 | -// SetMessageHandler() sets the message-handling callback |
841 | -func (svc *Service) SetMessageHandler(callback func([]byte) error) { |
842 | - svc.lock.Lock() |
843 | - defer svc.lock.Unlock() |
844 | - svc.msgHandler = callback |
845 | -} |
846 | - |
847 | -// GetMessageHandler() returns the (possibly nil) messaging handler callback |
848 | -func (svc *Service) GetMessageHandler() func([]byte) error { |
849 | - svc.lock.RLock() |
850 | - defer svc.lock.RUnlock() |
851 | - return svc.msgHandler |
852 | -} |
853 | - |
854 | -// IsRunning() returns whether the service's state is StateRunning |
855 | -func (svc *Service) IsRunning() bool { |
856 | - svc.lock.RLock() |
857 | - defer svc.lock.RUnlock() |
858 | - return svc.state == StateRunning |
859 | +// NewPushService() builds a new service and returns it. |
860 | +func NewPushService(bus bus.Endpoint, log logger.Logger) *PushService { |
861 | + var svc = &PushService{} |
862 | + svc.Log = log |
863 | + svc.Bus = bus |
864 | + return svc |
865 | +} |
866 | + |
867 | +// SetRegistrationURL() sets the registration url for the service |
868 | +func (svc *PushService) SetRegistrationURL(url string) { |
869 | + svc.lock.Lock() |
870 | + defer svc.lock.Unlock() |
871 | + svc.regURL = url |
872 | +} |
873 | + |
874 | +// SetAuthGetter() sets the authorization getter for the service |
875 | +func (svc *PushService) SetAuthGetter(authGetter func(string) string) { |
876 | + svc.lock.Lock() |
877 | + defer svc.lock.Unlock() |
878 | + svc.authGetter = authGetter |
879 | +} |
880 | + |
881 | +// GetRegistrationAuthorization() returns the authorization header for |
882 | +// POSTing to the registration HTTP endpoint |
883 | +func (svc *PushService) GetRegistrationAuthorization() string { |
884 | + svc.lock.RLock() |
885 | + defer svc.lock.RUnlock() |
886 | + if svc.authGetter != nil && svc.regURL != "" { |
887 | + return svc.authGetter(svc.regURL) |
888 | + } else { |
889 | + return "" |
890 | + } |
891 | } |
892 | |
893 | // Start() dials the bus, grab the name, and listens for method calls. |
894 | -func (svc *Service) Start() error { |
895 | - svc.lock.Lock() |
896 | - defer svc.lock.Unlock() |
897 | - if svc.state != StateUnknown { |
898 | - return AlreadyStarted |
899 | - } |
900 | - if svc.Log == nil || svc.Bus == nil { |
901 | - return NotConfigured |
902 | - } |
903 | - err := svc.Bus.Dial() |
904 | - if err != nil { |
905 | - return err |
906 | - } |
907 | - ch := svc.Bus.GrabName(true) |
908 | - log := svc.Log |
909 | - go func() { |
910 | - for err := range ch { |
911 | - if !svc.IsRunning() { |
912 | - break |
913 | - } |
914 | - if err != nil { |
915 | - log.Fatalf("name channel for %s got: %v", |
916 | - BusAddress.Name, err) |
917 | - } |
918 | - } |
919 | - }() |
920 | - svc.Bus.WatchMethod(bus.DispatchMap{ |
921 | - "Register": svc.register, |
922 | - "Notifications": svc.notifications, |
923 | - "Inject": svc.inject, |
924 | - }, svc) |
925 | - svc.state = StateRunning |
926 | - return nil |
927 | -} |
928 | - |
929 | -// Stop() closes the bus and sets the state to StateFinished |
930 | -func (svc *Service) Stop() { |
931 | - svc.lock.Lock() |
932 | - defer svc.lock.Unlock() |
933 | - if svc.Bus != nil { |
934 | - svc.Bus.Close() |
935 | - } |
936 | - svc.state = StateFinished |
937 | -} |
938 | - |
939 | -var ( |
940 | - BadArgCount = errors.New("Wrong number of arguments") |
941 | - BadArgType = errors.New("Bad argument type") |
942 | -) |
943 | - |
944 | -func (svc *Service) register(args []interface{}, _ []interface{}) ([]interface{}, error) { |
945 | - if len(args) != 1 { |
946 | +func (svc *PushService) Start() error { |
947 | + return svc.DBusService.Start(bus.DispatchMap{ |
948 | + "Register": svc.register, |
949 | + }, PushServiceBusAddress) |
950 | +} |
951 | + |
952 | +func (svc *PushService) register(path string, args, _ []interface{}) ([]interface{}, error) { |
953 | + if len(args) != 0 { |
954 | return nil, BadArgCount |
955 | } |
956 | - appname, ok := args[0].(string) |
957 | - if !ok { |
958 | - return nil, BadArgType |
959 | - } |
960 | + raw_appname := path[strings.LastIndex(path, "/")+1:] |
961 | + appname := string(nih.Unquote([]byte(raw_appname))) |
962 | |
963 | - rv := os.Getenv("PUSH_REG_" + appname) |
964 | + rv := os.Getenv("PUSH_REG_" + raw_appname) |
965 | if rv == "" { |
966 | - rv = "this-is-an-opaque-block-of-random-bits-i-promise" |
967 | + rv = appname + "::this-is-an-opaque-block-of-random-bits-i-promise" |
968 | } |
969 | |
970 | return []interface{}{rv}, nil |
971 | } |
972 | - |
973 | -func (svc *Service) notifications(args []interface{}, _ []interface{}) ([]interface{}, error) { |
974 | - if len(args) != 1 { |
975 | - return nil, BadArgCount |
976 | - } |
977 | - appname, ok := args[0].(string) |
978 | - if !ok { |
979 | - return nil, BadArgType |
980 | - } |
981 | - |
982 | - svc.lock.Lock() |
983 | - defer svc.lock.Unlock() |
984 | - |
985 | - if svc.mbox == nil { |
986 | - return []interface{}{[]string(nil)}, nil |
987 | - } |
988 | - msgs := svc.mbox[appname] |
989 | - delete(svc.mbox, appname) |
990 | - |
991 | - return []interface{}{msgs}, nil |
992 | -} |
993 | - |
994 | -func (svc *Service) inject(args []interface{}, _ []interface{}) ([]interface{}, error) { |
995 | - if len(args) != 2 { |
996 | - return nil, BadArgCount |
997 | - } |
998 | - appname, ok := args[0].(string) |
999 | - if !ok { |
1000 | - return nil, BadArgType |
1001 | - } |
1002 | - notif, ok := args[1].(string) |
1003 | - if !ok { |
1004 | - return nil, BadArgType |
1005 | - } |
1006 | - |
1007 | - return nil, svc.Inject(appname, notif) |
1008 | -} |
1009 | - |
1010 | -// Inject() signals to an application over dbus that a notification |
1011 | -// has arrived. |
1012 | -func (svc *Service) Inject(appname string, notif string) error { |
1013 | - svc.lock.Lock() |
1014 | - defer svc.lock.Unlock() |
1015 | - if svc.mbox == nil { |
1016 | - svc.mbox = make(map[string][]string) |
1017 | - } |
1018 | - svc.mbox[appname] = append(svc.mbox[appname], notif) |
1019 | - if svc.msgHandler != nil { |
1020 | - err := svc.msgHandler([]byte(notif)) |
1021 | - if err != nil { |
1022 | - svc.Log.Errorf("msgHandler returned %v", err) |
1023 | - return err |
1024 | - } |
1025 | - svc.Log.Debugf("call to msgHandler successful") |
1026 | - } |
1027 | - |
1028 | - return svc.Bus.Signal("Notification", []interface{}{appname}) |
1029 | -} |
1030 | |
1031 | === modified file 'client/service/service_test.go' |
1032 | --- client/service/service_test.go 2014-05-21 10:05:24 +0000 |
1033 | +++ client/service/service_test.go 2014-06-20 13:02:58 +0000 |
1034 | @@ -17,7 +17,7 @@ |
1035 | package service |
1036 | |
1037 | import ( |
1038 | - "errors" |
1039 | + "fmt" |
1040 | "os" |
1041 | "testing" |
1042 | |
1043 | @@ -45,7 +45,7 @@ |
1044 | } |
1045 | |
1046 | func (ss *serviceSuite) TestStart(c *C) { |
1047 | - svc := NewService(ss.bus, ss.log) |
1048 | + svc := NewPushService(ss.bus, ss.log) |
1049 | c.Check(svc.IsRunning(), Equals, false) |
1050 | c.Check(svc.Start(), IsNil) |
1051 | c.Check(svc.IsRunning(), Equals, true) |
1052 | @@ -53,31 +53,31 @@ |
1053 | } |
1054 | |
1055 | func (ss *serviceSuite) TestStartTwice(c *C) { |
1056 | - svc := NewService(ss.bus, ss.log) |
1057 | + svc := NewPushService(ss.bus, ss.log) |
1058 | c.Check(svc.Start(), IsNil) |
1059 | c.Check(svc.Start(), Equals, AlreadyStarted) |
1060 | svc.Stop() |
1061 | } |
1062 | |
1063 | func (ss *serviceSuite) TestStartNoLog(c *C) { |
1064 | - svc := NewService(ss.bus, nil) |
1065 | + svc := NewPushService(ss.bus, nil) |
1066 | c.Check(svc.Start(), Equals, NotConfigured) |
1067 | } |
1068 | |
1069 | func (ss *serviceSuite) TestStartNoBus(c *C) { |
1070 | - svc := NewService(nil, ss.log) |
1071 | + svc := NewPushService(nil, ss.log) |
1072 | c.Check(svc.Start(), Equals, NotConfigured) |
1073 | } |
1074 | |
1075 | func (ss *serviceSuite) TestStartFailsOnBusDialFailure(c *C) { |
1076 | bus := testibus.NewTestingEndpoint(condition.Work(false), nil) |
1077 | - svc := NewService(bus, ss.log) |
1078 | + svc := NewPushService(bus, ss.log) |
1079 | c.Check(svc.Start(), ErrorMatches, `.*(?i)cond said no.*`) |
1080 | svc.Stop() |
1081 | } |
1082 | |
1083 | func (ss *serviceSuite) TestStartGrabsName(c *C) { |
1084 | - svc := NewService(ss.bus, ss.log) |
1085 | + svc := NewPushService(ss.bus, ss.log) |
1086 | c.Assert(svc.Start(), IsNil) |
1087 | callArgs := testibus.GetCallArgs(ss.bus) |
1088 | defer svc.Stop() |
1089 | @@ -86,7 +86,7 @@ |
1090 | } |
1091 | |
1092 | func (ss *serviceSuite) TestStopClosesBus(c *C) { |
1093 | - svc := NewService(ss.bus, ss.log) |
1094 | + svc := NewPushService(ss.bus, ss.log) |
1095 | c.Assert(svc.Start(), IsNil) |
1096 | svc.Stop() |
1097 | callArgs := testibus.GetCallArgs(ss.bus) |
1098 | @@ -96,24 +96,45 @@ |
1099 | |
1100 | // registration tests |
1101 | |
1102 | +func (ss *serviceSuite) TestSetRegURLWorks(c *C) { |
1103 | + svc := NewPushService(ss.bus, ss.log) |
1104 | + c.Check(svc.regURL, Equals, "") |
1105 | + svc.SetRegistrationURL("xyzzy://") |
1106 | + c.Check(svc.regURL, Equals, "xyzzy://") |
1107 | +} |
1108 | + |
1109 | +func (ss *serviceSuite) TestSetAuthGetterWorks(c *C) { |
1110 | + svc := NewPushService(ss.bus, ss.log) |
1111 | + c.Check(svc.authGetter, IsNil) |
1112 | + f := func(string) string { return "" } |
1113 | + svc.SetAuthGetter(f) |
1114 | + c.Check(fmt.Sprintf("%#v", svc.authGetter), Equals, fmt.Sprintf("%#v", f)) |
1115 | +} |
1116 | + |
1117 | +func (ss *serviceSuite) TestGetRegAuthWorks(c *C) { |
1118 | + svc := NewPushService(ss.bus, ss.log) |
1119 | + svc.SetRegistrationURL("xyzzy://") |
1120 | + ch := make(chan string, 1) |
1121 | + f := func(s string) string { ch <- s; return "Auth " + s } |
1122 | + svc.SetAuthGetter(f) |
1123 | + c.Check(svc.GetRegistrationAuthorization(), Equals, "Auth xyzzy://") |
1124 | + c.Assert(len(ch), Equals, 1) |
1125 | + c.Check(<-ch, Equals, "xyzzy://") |
1126 | +} |
1127 | + |
1128 | +func (ss *serviceSuite) TestGetRegAuthDoesNotPanic(c *C) { |
1129 | + svc := NewPushService(ss.bus, ss.log) |
1130 | + c.Check(svc.GetRegistrationAuthorization(), Equals, "") |
1131 | +} |
1132 | + |
1133 | func (ss *serviceSuite) TestRegistrationFailsIfBadArgs(c *C) { |
1134 | - for i, s := range []struct { |
1135 | - args []interface{} |
1136 | - errt error |
1137 | - }{ |
1138 | - {nil, BadArgCount}, // no args |
1139 | - {[]interface{}{}, BadArgCount}, // still no args |
1140 | - {[]interface{}{42}, BadArgType}, // bad arg type |
1141 | - {[]interface{}{1, 2}, BadArgCount}, // too many args |
1142 | - } { |
1143 | - reg, err := new(Service).register(s.args, nil) |
1144 | - c.Check(reg, IsNil, Commentf("iteration #%d", i)) |
1145 | - c.Check(err, Equals, s.errt, Commentf("iteration #%d", i)) |
1146 | - } |
1147 | + reg, err := new(PushService).register("", []interface{}{1}, nil) |
1148 | + c.Check(reg, IsNil) |
1149 | + c.Check(err, Equals, BadArgCount) |
1150 | } |
1151 | |
1152 | func (ss *serviceSuite) TestRegistrationWorks(c *C) { |
1153 | - reg, err := new(Service).register([]interface{}{"this"}, nil) |
1154 | + reg, err := new(PushService).register("/this", nil, nil) |
1155 | c.Assert(reg, HasLen, 1) |
1156 | regs, ok := reg[0].(string) |
1157 | c.Check(ok, Equals, true) |
1158 | @@ -125,123 +146,10 @@ |
1159 | os.Setenv("PUSH_REG_stuff", "42") |
1160 | defer os.Setenv("PUSH_REG_stuff", "") |
1161 | |
1162 | - reg, err := new(Service).register([]interface{}{"stuff"}, nil) |
1163 | + reg, err := new(PushService).register("/stuff", nil, nil) |
1164 | c.Assert(reg, HasLen, 1) |
1165 | regs, ok := reg[0].(string) |
1166 | c.Check(ok, Equals, true) |
1167 | c.Check(regs, Equals, "42") |
1168 | c.Check(err, IsNil) |
1169 | } |
1170 | - |
1171 | -// |
1172 | -// Injection tests |
1173 | - |
1174 | -func (ss *serviceSuite) TestInjectWorks(c *C) { |
1175 | - svc := NewService(ss.bus, ss.log) |
1176 | - rvs, err := svc.inject([]interface{}{"hello", "world"}, nil) |
1177 | - c.Assert(err, IsNil) |
1178 | - c.Check(rvs, IsNil) |
1179 | - rvs, err = svc.inject([]interface{}{"hello", "there"}, nil) |
1180 | - c.Assert(err, IsNil) |
1181 | - c.Check(rvs, IsNil) |
1182 | - c.Assert(svc.mbox, HasLen, 1) |
1183 | - c.Assert(svc.mbox["hello"], HasLen, 2) |
1184 | - c.Check(svc.mbox["hello"][0], Equals, "world") |
1185 | - c.Check(svc.mbox["hello"][1], Equals, "there") |
1186 | - |
1187 | - // and check it fired the right signal (twice) |
1188 | - callArgs := testibus.GetCallArgs(ss.bus) |
1189 | - c.Assert(callArgs, HasLen, 2) |
1190 | - c.Check(callArgs[0].Member, Equals, "::Signal") |
1191 | - c.Check(callArgs[0].Args, DeepEquals, []interface{}{"Notification", []interface{}{"hello"}}) |
1192 | - c.Check(callArgs[1], DeepEquals, callArgs[0]) |
1193 | -} |
1194 | - |
1195 | -func (ss *serviceSuite) TestInjectFailsIfInjectFails(c *C) { |
1196 | - bus := testibus.NewTestingEndpoint(condition.Work(true), |
1197 | - condition.Work(false)) |
1198 | - svc := NewService(bus, ss.log) |
1199 | - svc.SetMessageHandler(func([]byte) error { return errors.New("fail") }) |
1200 | - _, err := svc.inject([]interface{}{"hello", "xyzzy"}, nil) |
1201 | - c.Check(err, NotNil) |
1202 | -} |
1203 | - |
1204 | -func (ss *serviceSuite) TestInjectFailsIfBadArgs(c *C) { |
1205 | - for i, s := range []struct { |
1206 | - args []interface{} |
1207 | - errt error |
1208 | - }{ |
1209 | - {nil, BadArgCount}, |
1210 | - {[]interface{}{}, BadArgCount}, |
1211 | - {[]interface{}{1}, BadArgCount}, |
1212 | - {[]interface{}{1, 2}, BadArgType}, |
1213 | - {[]interface{}{"1", 2}, BadArgType}, |
1214 | - {[]interface{}{1, "2"}, BadArgType}, |
1215 | - {[]interface{}{1, 2, 3}, BadArgCount}, |
1216 | - } { |
1217 | - reg, err := new(Service).inject(s.args, nil) |
1218 | - c.Check(reg, IsNil, Commentf("iteration #%d", i)) |
1219 | - c.Check(err, Equals, s.errt, Commentf("iteration #%d", i)) |
1220 | - } |
1221 | -} |
1222 | - |
1223 | -// |
1224 | -// Notifications tests |
1225 | -func (ss *serviceSuite) TestNotificationsWorks(c *C) { |
1226 | - svc := NewService(ss.bus, ss.log) |
1227 | - nots, err := svc.notifications([]interface{}{"hello"}, nil) |
1228 | - c.Assert(err, IsNil) |
1229 | - c.Assert(nots, NotNil) |
1230 | - c.Assert(nots, HasLen, 1) |
1231 | - c.Check(nots[0], HasLen, 0) |
1232 | - if svc.mbox == nil { |
1233 | - svc.mbox = make(map[string][]string) |
1234 | - } |
1235 | - svc.mbox["hello"] = append(svc.mbox["hello"], "this", "thing") |
1236 | - nots, err = svc.notifications([]interface{}{"hello"}, nil) |
1237 | - c.Assert(err, IsNil) |
1238 | - c.Assert(nots, NotNil) |
1239 | - c.Assert(nots, HasLen, 1) |
1240 | - c.Check(nots[0], DeepEquals, []string{"this", "thing"}) |
1241 | -} |
1242 | - |
1243 | -func (ss *serviceSuite) TestNotificationsFailsIfBadArgs(c *C) { |
1244 | - for i, s := range []struct { |
1245 | - args []interface{} |
1246 | - errt error |
1247 | - }{ |
1248 | - {nil, BadArgCount}, // no args |
1249 | - {[]interface{}{}, BadArgCount}, // still no args |
1250 | - {[]interface{}{42}, BadArgType}, // bad arg type |
1251 | - {[]interface{}{1, 2}, BadArgCount}, // too many args |
1252 | - } { |
1253 | - reg, err := new(Service).notifications(s.args, nil) |
1254 | - c.Check(reg, IsNil, Commentf("iteration #%d", i)) |
1255 | - c.Check(err, Equals, s.errt, Commentf("iteration #%d", i)) |
1256 | - } |
1257 | -} |
1258 | - |
1259 | -func (ss *serviceSuite) TestMessageHandler(c *C) { |
1260 | - svc := new(Service) |
1261 | - c.Assert(svc.msgHandler, IsNil) |
1262 | - var ext = []byte{} |
1263 | - e := errors.New("Hello") |
1264 | - f := func(s []byte) error { ext = s; return e } |
1265 | - c.Check(svc.GetMessageHandler(), IsNil) |
1266 | - svc.SetMessageHandler(f) |
1267 | - c.Check(svc.GetMessageHandler(), NotNil) |
1268 | - c.Check(svc.msgHandler([]byte("37")), Equals, e) |
1269 | - c.Check(ext, DeepEquals, []byte("37")) |
1270 | -} |
1271 | - |
1272 | -func (ss *serviceSuite) TestInjectCallsMessageHandler(c *C) { |
1273 | - var ext = []byte{} |
1274 | - svc := NewService(ss.bus, ss.log) |
1275 | - f := func(s []byte) error { ext = s; return nil } |
1276 | - svc.SetMessageHandler(f) |
1277 | - c.Check(svc.Inject("stuff", "{}"), IsNil) |
1278 | - c.Check(ext, DeepEquals, []byte("{}")) |
1279 | - err := errors.New("ouch") |
1280 | - svc.SetMessageHandler(func([]byte) error { return err }) |
1281 | - c.Check(svc.Inject("stuff", "{}"), Equals, err) |
1282 | -} |
1283 | |
1284 | === modified file 'client/session/session.go' |
1285 | --- client/session/session.go 2014-05-23 05:59:38 +0000 |
1286 | +++ client/session/session.go 2014-06-20 13:02:58 +0000 |
1287 | @@ -26,7 +26,6 @@ |
1288 | "fmt" |
1289 | "math/rand" |
1290 | "net" |
1291 | - "os/exec" |
1292 | "strings" |
1293 | "sync" |
1294 | "sync/atomic" |
1295 | @@ -87,7 +86,8 @@ |
1296 | ExpectAllRepairedTime time.Duration |
1297 | PEM []byte |
1298 | Info map[string]interface{} |
1299 | - AuthHelper []string |
1300 | + AuthGetter func(string) string |
1301 | + AuthURL string |
1302 | } |
1303 | |
1304 | // ClientSession holds a client<->server session and its configuration. |
1305 | @@ -244,21 +244,10 @@ |
1306 | // addAuthorization gets the authorization blob to send to the server |
1307 | // and adds it to the session. |
1308 | func (sess *ClientSession) addAuthorization() error { |
1309 | - sess.Log.Debugf("adding authorization") |
1310 | - // using a helper, for now at least |
1311 | - if len(sess.AuthHelper) == 0 { |
1312 | - // do nothing if helper is unset or empty |
1313 | - return nil |
1314 | - } |
1315 | - |
1316 | - auth, err := exec.Command(sess.AuthHelper[0], sess.AuthHelper[1:]...).Output() |
1317 | - if err != nil { |
1318 | - // For now we just log the error, as we don't want to block unauthorized users |
1319 | - sess.Log.Errorf("unable to get the authorization token from the account: %v", err) |
1320 | - } else { |
1321 | - sess.auth = strings.TrimSpace(string(auth)) |
1322 | - } |
1323 | - |
1324 | + if sess.AuthGetter != nil { |
1325 | + sess.Log.Debugf("adding authorization") |
1326 | + sess.auth = sess.AuthGetter(sess.AuthURL) |
1327 | + } |
1328 | return nil |
1329 | } |
1330 | |
1331 | |
1332 | === modified file 'client/session/session_test.go' |
1333 | --- client/session/session_test.go 2014-05-23 05:59:38 +0000 |
1334 | +++ client/session/session_test.go 2014-06-20 13:02:58 +0000 |
1335 | @@ -353,34 +353,21 @@ |
1336 | ****************************************************************/ |
1337 | |
1338 | func (cs *clientSessionSuite) TestAddAuthorizationAddsAuthorization(c *C) { |
1339 | - sess := &ClientSession{Log: cs.log} |
1340 | - sess.AuthHelper = []string{"echo", "some auth"} |
1341 | - c.Assert(sess.auth, Equals, "") |
1342 | - err := sess.addAuthorization() |
1343 | - c.Assert(err, IsNil) |
1344 | - c.Check(sess.auth, Equals, "some auth") |
1345 | -} |
1346 | - |
1347 | -func (cs *clientSessionSuite) TestAddAuthorizationIgnoresErrors(c *C) { |
1348 | - sess := &ClientSession{Log: cs.log} |
1349 | - sess.AuthHelper = []string{"sh", "-c", "echo hello; false"} |
1350 | - |
1351 | - c.Assert(sess.auth, Equals, "") |
1352 | - err := sess.addAuthorization() |
1353 | - c.Assert(err, IsNil) |
1354 | - c.Check(sess.auth, Equals, "") |
1355 | -} |
1356 | - |
1357 | -func (cs *clientSessionSuite) TestAddAuthorizationSkipsIfUnsetOrNil(c *C) { |
1358 | - sess := &ClientSession{Log: cs.log} |
1359 | - sess.AuthHelper = nil |
1360 | - c.Assert(sess.auth, Equals, "") |
1361 | - err := sess.addAuthorization() |
1362 | - c.Assert(err, IsNil) |
1363 | - c.Check(sess.auth, Equals, "") |
1364 | - |
1365 | - sess.AuthHelper = []string{} |
1366 | - err = sess.addAuthorization() |
1367 | + url := "xyzzy://" |
1368 | + sess := &ClientSession{Log: cs.log} |
1369 | + sess.AuthGetter = func(url string) string { return url + " auth'ed" } |
1370 | + sess.AuthURL = url |
1371 | + c.Assert(sess.auth, Equals, "") |
1372 | + err := sess.addAuthorization() |
1373 | + c.Assert(err, IsNil) |
1374 | + c.Check(sess.auth, Equals, "xyzzy:// auth'ed") |
1375 | +} |
1376 | + |
1377 | +func (cs *clientSessionSuite) TestAddAuthorizationSkipsIfUnset(c *C) { |
1378 | + sess := &ClientSession{Log: cs.log} |
1379 | + sess.AuthGetter = nil |
1380 | + c.Assert(sess.auth, Equals, "") |
1381 | + err := sess.addAuthorization() |
1382 | c.Assert(err, IsNil) |
1383 | c.Check(sess.auth, Equals, "") |
1384 | } |
1385 | |
1386 | === modified file 'debian/changelog' |
1387 | --- debian/changelog 2014-06-05 09:42:22 +0000 |
1388 | +++ debian/changelog 2014-06-20 13:02:58 +0000 |
1389 | @@ -1,3 +1,28 @@ |
1390 | +ubuntu-push (0.31ubuntu1) UNRELEASED; urgency=medium |
1391 | + |
1392 | + [ Samuele Pedroni ] |
1393 | + * Support registering tokens and sending notifications with a token |
1394 | + * Register script and scripts unicast support |
1395 | + |
1396 | + [ Roberto Alsina ] |
1397 | + * Make signing-helper generate a HTTP header instead of a querystring, |
1398 | + and take a URL to sign. |
1399 | + |
1400 | + [ John R. Lenton ] |
1401 | + * Switch dbus api to retrieve app name from dbus path. |
1402 | + * Move signing bits up from session to client, for reuse by service. |
1403 | + * Change AuthHelper to be a string; auth helper should now expect a |
1404 | + parameter (the url to sign). Added SessionURL to config. |
1405 | + * Adapt our whoopsie wrapper to whoopsie's now more correct behavior wrt |
1406 | + failing to get a mac address. |
1407 | + * Add registration_url to config; hook up auth bits and reg url to |
1408 | + client & service. |
1409 | + |
1410 | + [ Guillermo Gonzalez ] |
1411 | + * Split DBus service into PushService and PostalService |
1412 | + |
1413 | + -- John R. Lenton <john.lenton@canonical.com> Fri, 06 Jun 2014 12:02:56 +0100 |
1414 | + |
1415 | ubuntu-push (0.3+14.10.20140605-0ubuntu1) utopic; urgency=medium |
1416 | |
1417 | [ John Lenton ] |
1418 | |
1419 | === modified file 'debian/config.json' |
1420 | --- debian/config.json 2014-06-04 12:18:42 +0000 |
1421 | +++ debian/config.json 2014-06-20 13:02:58 +0000 |
1422 | @@ -1,5 +1,7 @@ |
1423 | { |
1424 | - "auth_helper": [], |
1425 | + "auth_helper": "/usr/lib/ubuntu-push-client/signing-helper", |
1426 | + "session_url": "https://push.ubuntu.com/", |
1427 | + "registration_url": "https://push.ubuntu.com/registration", |
1428 | "connect_timeout": "20s", |
1429 | "exchange_timeout": "30s", |
1430 | "hosts_cache_expiry": "12h", |
1431 | |
1432 | === modified file 'debian/rules' |
1433 | --- debian/rules 2014-05-02 12:42:27 +0000 |
1434 | +++ debian/rules 2014-06-20 13:02:58 +0000 |
1435 | @@ -5,7 +5,6 @@ |
1436 | export UBUNTU_PUSH_TEST_RESOURCES_ROOT := $(CURDIR) |
1437 | |
1438 | override_dh_auto_build: |
1439 | - cd $$( find ./ -type d -regex '\./[^/]*/src/launchpad.net' -printf "%h\n" | head -n1) |
1440 | dh_auto_build --buildsystem=golang |
1441 | (cd signing-helper && cmake . && make) |
1442 | |
1443 | |
1444 | === modified file 'http13client/Makefile' |
1445 | --- http13client/Makefile 2014-03-20 12:15:36 +0000 |
1446 | +++ http13client/Makefile 2014-06-20 13:02:58 +0000 |
1447 | @@ -20,7 +20,8 @@ |
1448 | |
1449 | prune: |
1450 | rm -rf example_test.go filetransport*.go fs*.go race.go range_test.go \ |
1451 | - sniff*.go httptest httputil testdata triv.go jar.go status.go |
1452 | + sniff*.go httptest httputil testdata triv.go jar.go status.go \ |
1453 | + cookie_test.go |
1454 | sed -i -e 's+"launchpad.net/ubuntu-push/http13client/+"net/http/+' *.go |
1455 | |
1456 | fix: |
1457 | |
1458 | === modified file 'http13client/_patches/empty_server.patch' |
1459 | --- http13client/_patches/empty_server.patch 2014-03-19 23:13:58 +0000 |
1460 | +++ http13client/_patches/empty_server.patch 2014-06-20 13:02:58 +0000 |
1461 | @@ -1,6 +1,6 @@ |
1462 | === modified file 'http13client/serve_test.go' |
1463 | ---- http13client/serve_test.go 2014-03-19 21:38:56 +0000 |
1464 | -+++ http13client/serve_test.go 2014-03-19 22:27:37 +0000 |
1465 | +--- http13client/serve_test.go 2014-06-20 11:00:47 +0000 |
1466 | ++++ http13client/serve_test.go 2014-06-20 12:00:22 +0000 |
1467 | @@ -2,60 +2,15 @@ |
1468 | // Use of this source code is governed by a BSD-style |
1469 | // license that can be found in the LICENSE file. |
1470 | @@ -62,7 +62,7 @@ |
1471 | |
1472 | func (a dummyAddr) Network() string { |
1473 | return string(a) |
1474 | -@@ -93,1289 +48,6 @@ |
1475 | +@@ -93,1325 +48,6 @@ |
1476 | return nil |
1477 | } |
1478 | |
1479 | @@ -906,31 +906,50 @@ |
1480 | -} |
1481 | - |
1482 | -type serverExpectTest struct { |
1483 | -- contentLength int // of request body |
1484 | +- contentLength int // of request body |
1485 | +- chunked bool |
1486 | - expectation string // e.g. "100-continue" |
1487 | - readBody bool // whether handler should read the body (if false, sends StatusUnauthorized) |
1488 | - expectedResponse string // expected substring in first line of http response |
1489 | -} |
1490 | - |
1491 | +-func expectTest(contentLength int, expectation string, readBody bool, expectedResponse string) serverExpectTest { |
1492 | +- return serverExpectTest{ |
1493 | +- contentLength: contentLength, |
1494 | +- expectation: expectation, |
1495 | +- readBody: readBody, |
1496 | +- expectedResponse: expectedResponse, |
1497 | +- } |
1498 | +-} |
1499 | +- |
1500 | -var serverExpectTests = []serverExpectTest{ |
1501 | - // Normal 100-continues, case-insensitive. |
1502 | -- {100, "100-continue", true, "100 Continue"}, |
1503 | -- {100, "100-cOntInUE", true, "100 Continue"}, |
1504 | +- expectTest(100, "100-continue", true, "100 Continue"), |
1505 | +- expectTest(100, "100-cOntInUE", true, "100 Continue"), |
1506 | - |
1507 | - // No 100-continue. |
1508 | -- {100, "", true, "200 OK"}, |
1509 | +- expectTest(100, "", true, "200 OK"), |
1510 | - |
1511 | - // 100-continue but requesting client to deny us, |
1512 | - // so it never reads the body. |
1513 | -- {100, "100-continue", false, "401 Unauthorized"}, |
1514 | +- expectTest(100, "100-continue", false, "401 Unauthorized"), |
1515 | - // Likewise without 100-continue: |
1516 | -- {100, "", false, "401 Unauthorized"}, |
1517 | +- expectTest(100, "", false, "401 Unauthorized"), |
1518 | - |
1519 | - // Non-standard expectations are failures |
1520 | -- {0, "a-pony", false, "417 Expectation Failed"}, |
1521 | +- expectTest(0, "a-pony", false, "417 Expectation Failed"), |
1522 | - |
1523 | -- // Expect-100 requested but no body |
1524 | -- {0, "100-continue", true, "400 Bad Request"}, |
1525 | +- // Expect-100 requested but no body (is apparently okay: Issue 7625) |
1526 | +- expectTest(0, "100-continue", true, "200 OK"), |
1527 | +- // Expect-100 requested but handler doesn't read the body |
1528 | +- expectTest(0, "100-continue", false, "401 Unauthorized"), |
1529 | +- // Expect-100 continue with no body, but a chunked body. |
1530 | +- { |
1531 | +- expectation: "100-continue", |
1532 | +- readBody: true, |
1533 | +- chunked: true, |
1534 | +- expectedResponse: "100 Continue", |
1535 | +- }, |
1536 | -} |
1537 | - |
1538 | -// Tests that the server responds to the "Expect" request header |
1539 | @@ -959,21 +978,38 @@ |
1540 | - |
1541 | - // Only send the body immediately if we're acting like an HTTP client |
1542 | - // that doesn't send 100-continue expectations. |
1543 | -- writeBody := test.contentLength > 0 && strings.ToLower(test.expectation) != "100-continue" |
1544 | +- writeBody := test.contentLength != 0 && strings.ToLower(test.expectation) != "100-continue" |
1545 | - |
1546 | - go func() { |
1547 | +- contentLen := fmt.Sprintf("Content-Length: %d", test.contentLength) |
1548 | +- if test.chunked { |
1549 | +- contentLen = "Transfer-Encoding: chunked" |
1550 | +- } |
1551 | - _, err := fmt.Fprintf(conn, "POST /?readbody=%v HTTP/1.1\r\n"+ |
1552 | - "Connection: close\r\n"+ |
1553 | -- "Content-Length: %d\r\n"+ |
1554 | +- "%s\r\n"+ |
1555 | - "Expect: %s\r\nHost: foo\r\n\r\n", |
1556 | -- test.readBody, test.contentLength, test.expectation) |
1557 | +- test.readBody, contentLen, test.expectation) |
1558 | - if err != nil { |
1559 | - t.Errorf("On test %#v, error writing request headers: %v", test, err) |
1560 | - return |
1561 | - } |
1562 | - if writeBody { |
1563 | +- var targ io.WriteCloser = struct { |
1564 | +- io.Writer |
1565 | +- io.Closer |
1566 | +- }{ |
1567 | +- conn, |
1568 | +- ioutil.NopCloser(nil), |
1569 | +- } |
1570 | +- if test.chunked { |
1571 | +- targ = httputil.NewChunkedWriter(conn) |
1572 | +- } |
1573 | - body := strings.Repeat("A", test.contentLength) |
1574 | -- _, err = fmt.Fprint(conn, body) |
1575 | +- _, err = fmt.Fprint(targ, body) |
1576 | +- if err == nil { |
1577 | +- err = targ.Close() |
1578 | +- } |
1579 | - if err != nil { |
1580 | - if !test.readBody { |
1581 | - // Server likely already hung up on us. |
1582 | @@ -1352,7 +1388,7 @@ |
1583 | type neverEnding byte |
1584 | |
1585 | func (b neverEnding) Read(p []byte) (n int, err error) { |
1586 | -@@ -1384,1344 +56,3 @@ |
1587 | +@@ -1420,1392 +56,3 @@ |
1588 | } |
1589 | return len(p), nil |
1590 | } |
1591 | @@ -2080,7 +2116,7 @@ |
1592 | - got := ht.rawResponse(req) |
1593 | - wantStatus := fmt.Sprintf("%d %s", code, StatusText(code)) |
1594 | - if !strings.Contains(got, wantStatus) { |
1595 | -- t.Errorf("Code %d: Wanted %q Modified for %q: %s", code, req, got) |
1596 | +- t.Errorf("Code %d: Wanted %q Modified for %q: %s", code, wantStatus, req, got) |
1597 | - } else if strings.Contains(got, "Content-Length") { |
1598 | - t.Errorf("Code %d: Got a Content-Length from %q: %s", code, req, got) |
1599 | - } else if strings.Contains(got, "stuff") { |
1600 | @@ -2090,6 +2126,21 @@ |
1601 | - } |
1602 | -} |
1603 | - |
1604 | +-func TestContentTypeOkayOn204(t *testing.T) { |
1605 | +- ht := newHandlerTest(HandlerFunc(func(w ResponseWriter, r *Request) { |
1606 | +- w.Header().Set("Content-Length", "123") // suppressed |
1607 | +- w.Header().Set("Content-Type", "foo/bar") |
1608 | +- w.WriteHeader(204) |
1609 | +- })) |
1610 | +- got := ht.rawResponse("GET / HTTP/1.1") |
1611 | +- if !strings.Contains(got, "Content-Type: foo/bar") { |
1612 | +- t.Errorf("Response = %q; want Content-Type: foo/bar", got) |
1613 | +- } |
1614 | +- if strings.Contains(got, "Content-Length: 123") { |
1615 | +- t.Errorf("Response = %q; don't want a Content-Length", got) |
1616 | +- } |
1617 | +-} |
1618 | +- |
1619 | -// Issue 6995 |
1620 | -// A server Handler can receive a Request, and then turn around and |
1621 | -// give a copy of that Request.Body out to the Transport (e.g. any |
1622 | @@ -2261,7 +2312,7 @@ |
1623 | - ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0) |
1624 | - ts.Config.ConnState = func(c net.Conn, state ConnState) { |
1625 | - if c == nil { |
1626 | -- t.Error("nil conn seen in state %s", state) |
1627 | +- t.Errorf("nil conn seen in state %s", state) |
1628 | - return |
1629 | - } |
1630 | - mu.Lock() |
1631 | @@ -2397,6 +2448,39 @@ |
1632 | - } |
1633 | -} |
1634 | - |
1635 | +-// golang.org/issue/7856 |
1636 | +-func TestServerEmptyBodyRace(t *testing.T) { |
1637 | +- defer afterTest(t) |
1638 | +- var n int32 |
1639 | +- ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { |
1640 | +- atomic.AddInt32(&n, 1) |
1641 | +- })) |
1642 | +- defer ts.Close() |
1643 | +- var wg sync.WaitGroup |
1644 | +- const reqs = 20 |
1645 | +- for i := 0; i < reqs; i++ { |
1646 | +- wg.Add(1) |
1647 | +- go func() { |
1648 | +- defer wg.Done() |
1649 | +- res, err := Get(ts.URL) |
1650 | +- if err != nil { |
1651 | +- t.Error(err) |
1652 | +- return |
1653 | +- } |
1654 | +- defer res.Body.Close() |
1655 | +- _, err = io.Copy(ioutil.Discard, res.Body) |
1656 | +- if err != nil { |
1657 | +- t.Error(err) |
1658 | +- return |
1659 | +- } |
1660 | +- }() |
1661 | +- } |
1662 | +- wg.Wait() |
1663 | +- if got := atomic.LoadInt32(&n); got != reqs { |
1664 | +- t.Errorf("handler ran %d times; want %d", got, reqs) |
1665 | +- } |
1666 | +-} |
1667 | +- |
1668 | -func TestServerConnStateNew(t *testing.T) { |
1669 | - sawNew := false // if the test is buggy, we'll race on this variable. |
1670 | - srv := &Server{ |
1671 | @@ -2699,9 +2783,9 @@ |
1672 | -} |
1673 | |
1674 | === modified file 'http13client/server.go' |
1675 | ---- http13client/server.go 2014-03-19 20:20:19 +0000 |
1676 | -+++ http13client/server.go 2014-03-19 22:27:37 +0000 |
1677 | -@@ -2,1984 +2,17 @@ |
1678 | +--- http13client/server.go 2014-06-20 11:00:47 +0000 |
1679 | ++++ http13client/server.go 2014-06-20 12:05:53 +0000 |
1680 | +@@ -2,1976 +2,16 @@ |
1681 | // Use of this source code is governed by a BSD-style |
1682 | // license that can be found in the LICENSE file. |
1683 | |
1684 | @@ -2723,7 +2807,7 @@ |
1685 | - "path" |
1686 | - "runtime" |
1687 | - "strconv" |
1688 | - "strings" |
1689 | +- "strings" |
1690 | "sync" |
1691 | - "sync/atomic" |
1692 | - "time" |
1693 | @@ -3506,18 +3590,16 @@ |
1694 | - } |
1695 | - |
1696 | - code := w.status |
1697 | -- if !bodyAllowedForStatus(code) { |
1698 | -- // Must not have body. |
1699 | -- // RFC 2616 section 10.3.5: "the response MUST NOT include other entity-headers" |
1700 | -- for _, k := range []string{"Content-Type", "Content-Length", "Transfer-Encoding"} { |
1701 | -- delHeader(k) |
1702 | -- } |
1703 | -- } else { |
1704 | +- if bodyAllowedForStatus(code) { |
1705 | - // If no content type, apply sniffing algorithm to body. |
1706 | - _, haveType := header["Content-Type"] |
1707 | - if !haveType { |
1708 | - setHeader.contentType = DetectContentType(p) |
1709 | - } |
1710 | +- } else { |
1711 | +- for _, k := range suppressedHeaders(code) { |
1712 | +- delHeader(k) |
1713 | +- } |
1714 | - } |
1715 | - |
1716 | - if _, ok := header["Date"]; !ok { |
1717 | @@ -3865,16 +3947,10 @@ |
1718 | - // Expect 100 Continue support |
1719 | - req := w.req |
1720 | - if req.expectsContinue() { |
1721 | -- if req.ProtoAtLeast(1, 1) { |
1722 | +- if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 { |
1723 | - // Wrap the Body reader with one that replies on the connection |
1724 | - req.Body = &expectContinueReader{readCloser: req.Body, resp: w} |
1725 | - } |
1726 | -- if req.ContentLength == 0 { |
1727 | -- w.Header().Set("Connection", "close") |
1728 | -- w.WriteHeader(StatusBadRequest) |
1729 | -- w.finishRequest() |
1730 | -- break |
1731 | -- } |
1732 | - req.Header.Del("Expect") |
1733 | - } else if req.Header.get("Expect") != "" { |
1734 | - w.sendExpectationFailed() |
1735 | @@ -4685,11 +4761,11 @@ |
1736 | -} |
1737 | +) |
1738 | |
1739 | - // eofReader is a non-nil io.ReadCloser that always returns EOF. |
1740 | - // It embeds a *strings.Reader so it still has a WriteTo method |
1741 | -@@ -1992,28 +25,6 @@ |
1742 | - ioutil.NopCloser(nil), |
1743 | - } |
1744 | + type eofReaderWithWriteTo struct{} |
1745 | + |
1746 | +@@ -1991,28 +31,6 @@ |
1747 | + // Verify that an io.Copy from an eofReader won't require a buffer. |
1748 | + var _ io.WriterTo = eofReader |
1749 | |
1750 | -// initNPNRequest is an HTTP handler that initializes certain |
1751 | -// uninitialized fields in its *Request. Such partially-initialized |
1752 | |
1753 | === modified file 'http13client/_patches/fix_code.patch' |
1754 | --- http13client/_patches/fix_code.patch 2014-03-19 23:13:58 +0000 |
1755 | +++ http13client/_patches/fix_code.patch 2014-06-20 13:02:58 +0000 |
1756 | @@ -1,6 +1,6 @@ |
1757 | === modified file 'http13client/client.go' |
1758 | ---- http13client/client.go 2014-03-19 20:20:19 +0000 |
1759 | -+++ http13client/client.go 2014-03-19 22:27:37 +0000 |
1760 | +--- http13client/client.go 2014-06-20 11:00:47 +0000 |
1761 | ++++ http13client/client.go 2014-06-20 12:05:53 +0000 |
1762 | @@ -17,6 +17,7 @@ |
1763 | "io/ioutil" |
1764 | "log" |
1765 | @@ -18,7 +18,7 @@ |
1766 | |
1767 | // Timeout specifies a time limit for requests made by this |
1768 | // Client. The timeout includes connection time, any |
1769 | -@@ -177,7 +178,7 @@ |
1770 | +@@ -184,7 +185,7 @@ |
1771 | // Headers, leaving it uninitialized. We guarantee to the |
1772 | // Transport that this has been initialized, though. |
1773 | if req.Header == nil { |
1774 | @@ -27,7 +27,7 @@ |
1775 | } |
1776 | |
1777 | if u := req.URL.User; u != nil { |
1778 | -@@ -308,7 +309,7 @@ |
1779 | +@@ -316,7 +317,7 @@ |
1780 | if ireq.Method == "POST" || ireq.Method == "PUT" { |
1781 | nreq.Method = "GET" |
1782 | } |
1783 | @@ -38,8 +38,8 @@ |
1784 | break |
1785 | |
1786 | === modified file 'http13client/cookie.go' |
1787 | ---- http13client/cookie.go 2014-03-19 20:20:19 +0000 |
1788 | -+++ http13client/cookie.go 2014-03-19 22:27:37 +0000 |
1789 | +--- http13client/cookie.go 2014-06-20 11:00:47 +0000 |
1790 | ++++ http13client/cookie.go 2014-06-20 12:05:53 +0000 |
1791 | @@ -5,10 +5,9 @@ |
1792 | package http |
1793 | |
1794 | @@ -94,7 +94,7 @@ |
1795 | Name: name, |
1796 | Value: value, |
1797 | Raw: line, |
1798 | -@@ -129,59 +108,12 @@ |
1799 | +@@ -125,59 +104,12 @@ |
1800 | return cookies |
1801 | } |
1802 | |
1803 | @@ -156,7 +156,7 @@ |
1804 | lines, ok := h["Cookie"] |
1805 | if !ok { |
1806 | return cookies |
1807 | -@@ -213,7 +145,7 @@ |
1808 | +@@ -209,7 +141,7 @@ |
1809 | if !success { |
1810 | continue |
1811 | } |
1812 | @@ -167,8 +167,8 @@ |
1813 | } |
1814 | |
1815 | === modified file 'http13client/header.go' |
1816 | ---- http13client/header.go 2014-03-19 20:20:19 +0000 |
1817 | -+++ http13client/header.go 2014-03-19 22:27:37 +0000 |
1818 | +--- http13client/header.go 2014-06-20 11:00:47 +0000 |
1819 | ++++ http13client/header.go 2014-06-20 12:00:22 +0000 |
1820 | @@ -5,176 +5,9 @@ |
1821 | package http |
1822 | |
1823 | @@ -348,8 +348,8 @@ |
1824 | // token must be all lowercase. |
1825 | |
1826 | === modified file 'http13client/request.go' |
1827 | ---- http13client/request.go 2014-03-19 20:20:19 +0000 |
1828 | -+++ http13client/request.go 2014-03-19 22:27:37 +0000 |
1829 | +--- http13client/request.go 2014-06-20 11:00:47 +0000 |
1830 | ++++ http13client/request.go 2014-06-20 12:05:53 +0000 |
1831 | @@ -16,6 +16,7 @@ |
1832 | "io/ioutil" |
1833 | "mime" |
1834 | @@ -358,25 +358,25 @@ |
1835 | "net/textproto" |
1836 | "net/url" |
1837 | "strconv" |
1838 | -@@ -103,7 +104,7 @@ |
1839 | - // The request parser implements this by canonicalizing the |
1840 | - // name, making the first character and any characters |
1841 | - // following a hyphen uppercase and the rest lowercase. |
1842 | +@@ -121,7 +122,7 @@ |
1843 | + // added and may override values in Header. |
1844 | + // |
1845 | + // See the documentation for the Request.Write method. |
1846 | - Header Header |
1847 | + Header http.Header |
1848 | |
1849 | // Body is the request's body. |
1850 | // |
1851 | -@@ -164,7 +165,7 @@ |
1852 | - // For server requests, Trailer is only populated after Body has been |
1853 | - // closed or fully consumed. |
1854 | - // Trailer support is only partially complete. |
1855 | +@@ -199,7 +200,7 @@ |
1856 | + // not mutate Trailer. |
1857 | + // |
1858 | + // Few HTTP clients, servers, or proxies support HTTP trailers. |
1859 | - Trailer Header |
1860 | + Trailer http.Header |
1861 | |
1862 | // RemoteAddr allows HTTP servers and other software to record |
1863 | // the network address that sent the request, usually for |
1864 | -@@ -204,7 +205,7 @@ |
1865 | +@@ -239,7 +240,7 @@ |
1866 | } |
1867 | |
1868 | // Cookies parses and returns the HTTP cookies sent with the request. |
1869 | @@ -385,7 +385,7 @@ |
1870 | return readCookies(r.Header, "") |
1871 | } |
1872 | |
1873 | -@@ -212,7 +213,7 @@ |
1874 | +@@ -247,7 +248,7 @@ |
1875 | |
1876 | // Cookie returns the named cookie provided in the request or |
1877 | // ErrNoCookie if not found. |
1878 | @@ -394,7 +394,7 @@ |
1879 | for _, c := range readCookies(r.Header, name) { |
1880 | return c, nil |
1881 | } |
1882 | -@@ -223,7 +224,7 @@ |
1883 | +@@ -258,7 +259,7 @@ |
1884 | // AddCookie does not attach more than one Cookie header field. That |
1885 | // means all cookies, if any, are written into the same line, |
1886 | // separated by semicolon. |
1887 | @@ -403,7 +403,7 @@ |
1888 | s := fmt.Sprintf("%s=%s", sanitizeCookieName(c.Name), sanitizeCookieValue(c.Value)) |
1889 | if c := r.Header.Get("Cookie"); c != "" { |
1890 | r.Header.Set("Cookie", c+"; "+s) |
1891 | -@@ -326,7 +327,7 @@ |
1892 | +@@ -361,7 +362,7 @@ |
1893 | } |
1894 | |
1895 | // extraHeaders may be nil |
1896 | @@ -412,7 +412,7 @@ |
1897 | host := req.Host |
1898 | if host == "" { |
1899 | if req.URL == nil { |
1900 | -@@ -456,7 +457,7 @@ |
1901 | +@@ -490,7 +491,7 @@ |
1902 | Proto: "HTTP/1.1", |
1903 | ProtoMajor: 1, |
1904 | ProtoMinor: 1, |
1905 | @@ -421,7 +421,7 @@ |
1906 | Body: rc, |
1907 | Host: u.Host, |
1908 | } |
1909 | -@@ -571,7 +572,7 @@ |
1910 | +@@ -605,7 +606,7 @@ |
1911 | if err != nil { |
1912 | return nil, err |
1913 | } |
1914 | @@ -430,7 +430,7 @@ |
1915 | |
1916 | // RFC2616: Must treat |
1917 | // GET /index.html HTTP/1.1 |
1918 | -@@ -582,7 +583,7 @@ |
1919 | +@@ -616,7 +617,7 @@ |
1920 | // the same. In the second case, any Host line is ignored. |
1921 | req.Host = req.URL.Host |
1922 | if req.Host == "" { |
1923 | @@ -439,7 +439,7 @@ |
1924 | } |
1925 | delete(req.Header, "Host") |
1926 | |
1927 | -@@ -630,12 +631,12 @@ |
1928 | +@@ -638,12 +639,12 @@ |
1929 | // |
1930 | // MaxBytesReader prevents clients from accidentally or maliciously |
1931 | // sending a large request and wasting server resources. |
1932 | @@ -454,7 +454,7 @@ |
1933 | r io.ReadCloser // underlying reader |
1934 | n int64 // max bytes remaining |
1935 | stopped bool |
1936 | -@@ -645,9 +646,6 @@ |
1937 | +@@ -653,9 +654,6 @@ |
1938 | if l.n <= 0 { |
1939 | if !l.stopped { |
1940 | l.stopped = true |
1941 | @@ -464,7 +464,7 @@ |
1942 | } |
1943 | return 0, errors.New("http: request body too large") |
1944 | } |
1945 | -@@ -852,16 +850,16 @@ |
1946 | +@@ -858,18 +856,18 @@ |
1947 | } |
1948 | |
1949 | func (r *Request) expectsContinue() bool { |
1950 | @@ -484,11 +484,13 @@ |
1951 | - return hasToken(r.Header.get("Connection"), "close") |
1952 | + return hasToken(r.Header.Get("Connection"), "close") |
1953 | } |
1954 | + |
1955 | + func (r *Request) closeBody() { |
1956 | |
1957 | === modified file 'http13client/response.go' |
1958 | ---- http13client/response.go 2014-03-19 20:20:19 +0000 |
1959 | -+++ http13client/response.go 2014-03-19 22:27:37 +0000 |
1960 | -@@ -11,6 +11,7 @@ |
1961 | +--- http13client/response.go 2014-06-20 11:00:47 +0000 |
1962 | ++++ http13client/response.go 2014-06-20 12:05:53 +0000 |
1963 | +@@ -12,6 +12,7 @@ |
1964 | "crypto/tls" |
1965 | "errors" |
1966 | "io" |
1967 | @@ -496,7 +498,7 @@ |
1968 | "net/textproto" |
1969 | "net/url" |
1970 | "strconv" |
1971 | -@@ -40,7 +41,7 @@ |
1972 | +@@ -41,7 +42,7 @@ |
1973 | // omitted from Header. |
1974 | // |
1975 | // Keys in the map are canonicalized (see CanonicalHeaderKey). |
1976 | @@ -505,7 +507,7 @@ |
1977 | |
1978 | // Body represents the response body. |
1979 | // |
1980 | -@@ -69,7 +70,7 @@ |
1981 | +@@ -71,7 +72,7 @@ |
1982 | |
1983 | // Trailer maps trailer keys to values, in the same |
1984 | // format as the header. |
1985 | @@ -514,7 +516,7 @@ |
1986 | |
1987 | // The Request that was sent to obtain this Response. |
1988 | // Request's Body is nil (having already been consumed). |
1989 | -@@ -84,7 +85,7 @@ |
1990 | +@@ -86,7 +87,7 @@ |
1991 | } |
1992 | |
1993 | // Cookies parses and returns the cookies set in the Set-Cookie headers. |
1994 | @@ -523,7 +525,7 @@ |
1995 | return readSetCookies(r.Header) |
1996 | } |
1997 | |
1998 | -@@ -153,7 +154,7 @@ |
1999 | +@@ -155,7 +156,7 @@ |
2000 | } |
2001 | return nil, err |
2002 | } |
2003 | @@ -532,7 +534,7 @@ |
2004 | |
2005 | fixPragmaCacheControl(resp.Header) |
2006 | |
2007 | -@@ -169,7 +170,7 @@ |
2008 | +@@ -171,7 +172,7 @@ |
2009 | // Pragma: no-cache |
2010 | // like |
2011 | // Cache-Control: no-cache |
2012 | @@ -543,17 +545,17 @@ |
2013 | header["Cache-Control"] = []string{"no-cache"} |
2014 | |
2015 | === modified file 'http13client/transfer.go' |
2016 | ---- http13client/transfer.go 2014-03-19 20:20:19 +0000 |
2017 | -+++ http13client/transfer.go 2014-03-19 22:27:37 +0000 |
2018 | +--- http13client/transfer.go 2014-06-20 11:00:47 +0000 |
2019 | ++++ http13client/transfer.go 2014-06-20 12:05:53 +0000 |
2020 | @@ -11,6 +11,7 @@ |
2021 | "fmt" |
2022 | "io" |
2023 | "io/ioutil" |
2024 | + "net/http" |
2025 | "net/textproto" |
2026 | + "sort" |
2027 | "strconv" |
2028 | - "strings" |
2029 | -@@ -36,7 +37,7 @@ |
2030 | +@@ -37,7 +38,7 @@ |
2031 | ContentLength int64 // -1 means unknown, 0 means exactly none |
2032 | Close bool |
2033 | TransferEncoding []string |
2034 | @@ -562,16 +564,16 @@ |
2035 | } |
2036 | |
2037 | func newTransferWriter(r interface{}) (t *transferWriter, err error) { |
2038 | -@@ -174,7 +175,7 @@ |
2039 | - io.WriteString(w, "Trailer: ") |
2040 | - needComma := false |
2041 | +@@ -171,7 +172,7 @@ |
2042 | + if t.Trailer != nil { |
2043 | + keys := make([]string, 0, len(t.Trailer)) |
2044 | for k := range t.Trailer { |
2045 | - k = CanonicalHeaderKey(k) |
2046 | + k = http.CanonicalHeaderKey(k) |
2047 | switch k { |
2048 | case "Transfer-Encoding", "Trailer", "Content-Length": |
2049 | return &badStringError{"invalid Trailer key", k} |
2050 | -@@ -237,7 +238,7 @@ |
2051 | +@@ -243,7 +244,7 @@ |
2052 | |
2053 | type transferReader struct { |
2054 | // Input |
2055 | @@ -580,7 +582,7 @@ |
2056 | StatusCode int |
2057 | RequestMethod string |
2058 | ProtoMajor int |
2059 | -@@ -247,7 +248,7 @@ |
2060 | +@@ -253,7 +254,7 @@ |
2061 | ContentLength int64 |
2062 | TransferEncoding []string |
2063 | Close bool |
2064 | @@ -589,7 +591,7 @@ |
2065 | } |
2066 | |
2067 | // bodyAllowedForStatus reports whether a given response status code |
2068 | -@@ -308,7 +309,7 @@ |
2069 | +@@ -330,7 +331,7 @@ |
2070 | return err |
2071 | } |
2072 | if isResponse && t.RequestMethod == "HEAD" { |
2073 | @@ -598,7 +600,7 @@ |
2074 | return err |
2075 | } else { |
2076 | t.ContentLength = n |
2077 | -@@ -386,7 +387,7 @@ |
2078 | +@@ -408,7 +409,7 @@ |
2079 | func isIdentity(te []string) bool { return len(te) == 1 && te[0] == "identity" } |
2080 | |
2081 | // Sanitize transfer encoding |
2082 | @@ -607,7 +609,7 @@ |
2083 | raw, present := header["Transfer-Encoding"] |
2084 | if !present { |
2085 | return nil, nil |
2086 | -@@ -429,7 +430,7 @@ |
2087 | +@@ -451,7 +452,7 @@ |
2088 | // Determine the expected body length, using RFC 2616 Section 4.4. This |
2089 | // function is not a method, because ultimately it should be shared by |
2090 | // ReadResponse and ReadRequest. |
2091 | @@ -616,7 +618,7 @@ |
2092 | |
2093 | // Logic based on response type or status |
2094 | if noBodyExpected(requestMethod) { |
2095 | -@@ -449,7 +450,7 @@ |
2096 | +@@ -471,7 +472,7 @@ |
2097 | } |
2098 | |
2099 | // Logic based on Content-Length |
2100 | @@ -625,7 +627,7 @@ |
2101 | if cl != "" { |
2102 | n, err := parseContentLength(cl) |
2103 | if err != nil { |
2104 | -@@ -475,18 +476,18 @@ |
2105 | +@@ -497,18 +498,18 @@ |
2106 | // Determine whether to hang up after sending a request and body, or |
2107 | // receiving a response and body |
2108 | // 'header' is the request headers |
2109 | @@ -647,7 +649,7 @@ |
2110 | header.Del("Connection") |
2111 | return true |
2112 | } |
2113 | -@@ -495,17 +496,17 @@ |
2114 | +@@ -517,17 +518,17 @@ |
2115 | } |
2116 | |
2117 | // Parse the trailer header |
2118 | @@ -669,22 +671,28 @@ |
2119 | switch key { |
2120 | case "Transfer-Encoding", "Trailer", "Content-Length": |
2121 | return nil, &badStringError{"bad trailer key", key} |
2122 | -@@ -642,9 +643,9 @@ |
2123 | +@@ -664,14 +665,14 @@ |
2124 | } |
2125 | switch rr := b.hdr.(type) { |
2126 | case *Request: |
2127 | -- rr.Trailer = Header(hdr) |
2128 | -+ rr.Trailer = http.Header(hdr) |
2129 | +- mergeSetHeader(&rr.Trailer, Header(hdr)) |
2130 | ++ mergeSetHeader(&rr.Trailer, http.Header(hdr)) |
2131 | case *Response: |
2132 | -- rr.Trailer = Header(hdr) |
2133 | -+ rr.Trailer = http.Header(hdr) |
2134 | +- mergeSetHeader(&rr.Trailer, Header(hdr)) |
2135 | ++ mergeSetHeader(&rr.Trailer, http.Header(hdr)) |
2136 | } |
2137 | return nil |
2138 | } |
2139 | + |
2140 | +-func mergeSetHeader(dst *Header, src Header) { |
2141 | ++func mergeSetHeader(dst *http.Header, src http.Header) { |
2142 | + if *dst == nil { |
2143 | + *dst = src |
2144 | + return |
2145 | |
2146 | === modified file 'http13client/transport.go' |
2147 | ---- http13client/transport.go 2014-03-19 20:20:19 +0000 |
2148 | -+++ http13client/transport.go 2014-03-19 22:27:37 +0000 |
2149 | +--- http13client/transport.go 2014-06-20 11:00:47 +0000 |
2150 | ++++ http13client/transport.go 2014-06-20 12:05:53 +0000 |
2151 | @@ -18,6 +18,7 @@ |
2152 | "io" |
2153 | "log" |
2154 | @@ -693,7 +701,7 @@ |
2155 | "net/url" |
2156 | "os" |
2157 | "strings" |
2158 | -@@ -144,12 +145,12 @@ |
2159 | +@@ -147,12 +148,12 @@ |
2160 | // optional extra headers to write. |
2161 | type transportRequest struct { |
2162 | *Request // original request, not to be mutated |
2163 | @@ -709,7 +717,7 @@ |
2164 | } |
2165 | return tr.extra |
2166 | } |
2167 | -@@ -512,7 +513,7 @@ |
2168 | +@@ -519,7 +520,7 @@ |
2169 | case cm.targetScheme == "http": |
2170 | pconn.isProxy = true |
2171 | if pa != "" { |
2172 | @@ -718,7 +726,7 @@ |
2173 | h.Set("Proxy-Authorization", pa) |
2174 | } |
2175 | } |
2176 | -@@ -521,7 +522,7 @@ |
2177 | +@@ -528,7 +529,7 @@ |
2178 | Method: "CONNECT", |
2179 | URL: &url.URL{Opaque: cm.targetAddr}, |
2180 | Host: cm.targetAddr, |
2181 | @@ -727,7 +735,7 @@ |
2182 | } |
2183 | if pa != "" { |
2184 | connectReq.Header.Set("Proxy-Authorization", pa) |
2185 | -@@ -735,7 +736,7 @@ |
2186 | +@@ -748,7 +749,7 @@ |
2187 | // mutateHeaderFunc is an optional func to modify extra |
2188 | // headers on each outbound request before it's written. (the |
2189 | // original Request given to RoundTrip is not modified) |
2190 | @@ -735,5 +743,5 @@ |
2191 | + mutateHeaderFunc func(http.Header) |
2192 | } |
2193 | |
2194 | - func (pc *persistConn) isBroken() bool { |
2195 | + // isBroken reports whether this connection is in a known broken state. |
2196 | |
2197 | |
2198 | === modified file 'http13client/_patches/fix_status.patch' |
2199 | --- http13client/_patches/fix_status.patch 2014-03-19 23:43:25 +0000 |
2200 | +++ http13client/_patches/fix_status.patch 2014-06-20 13:02:58 +0000 |
2201 | @@ -1,7 +1,7 @@ |
2202 | === modified file 'http13client/client.go' |
2203 | ---- http13client/client.go 2014-03-19 23:13:58 +0000 |
2204 | -+++ http13client/client.go 2014-03-19 23:38:11 +0000 |
2205 | -@@ -210,7 +210,7 @@ |
2206 | +--- http13client/client.go 2014-06-20 12:46:25 +0000 |
2207 | ++++ http13client/client.go 2014-06-20 12:46:45 +0000 |
2208 | +@@ -217,7 +217,7 @@ |
2209 | // automatically redirect. |
2210 | func shouldRedirectGet(statusCode int) bool { |
2211 | switch statusCode { |
2212 | @@ -10,7 +10,7 @@ |
2213 | return true |
2214 | } |
2215 | return false |
2216 | -@@ -220,7 +220,7 @@ |
2217 | +@@ -227,7 +227,7 @@ |
2218 | // automatically redirect. |
2219 | func shouldRedirectPost(statusCode int) bool { |
2220 | switch statusCode { |
2221 | @@ -21,9 +21,9 @@ |
2222 | return false |
2223 | |
2224 | === modified file 'http13client/client_test.go' |
2225 | ---- http13client/client_test.go 2014-03-19 23:13:58 +0000 |
2226 | -+++ http13client/client_test.go 2014-03-19 23:39:48 +0000 |
2227 | -@@ -202,7 +202,7 @@ |
2228 | +--- http13client/client_test.go 2014-06-20 12:46:25 +0000 |
2229 | ++++ http13client/client_test.go 2014-06-20 12:46:45 +0000 |
2230 | +@@ -204,7 +204,7 @@ |
2231 | } |
2232 | } |
2233 | if n < 15 { |
2234 | @@ -32,7 +32,7 @@ |
2235 | return |
2236 | } |
2237 | fmt.Fprintf(w, "n=%d", n) |
2238 | -@@ -324,7 +324,7 @@ |
2239 | +@@ -326,7 +326,7 @@ |
2240 | } |
2241 | if r.URL.Path == "/" { |
2242 | http.SetCookie(w, expectedCookies[1]) |
2243 | @@ -41,7 +41,7 @@ |
2244 | } else { |
2245 | http.SetCookie(w, expectedCookies[2]) |
2246 | w.Write([]byte("hello")) |
2247 | -@@ -783,7 +783,7 @@ |
2248 | +@@ -785,7 +785,7 @@ |
2249 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
2250 | if r.URL.Path == "/" { |
2251 | sawRoot <- true |
2252 | @@ -50,7 +50,7 @@ |
2253 | return |
2254 | } |
2255 | if r.URL.Path == "/slow" { |
2256 | -@@ -844,7 +844,7 @@ |
2257 | +@@ -846,7 +846,7 @@ |
2258 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
2259 | saw <- r.RemoteAddr |
2260 | if r.URL.Path == "/" { |
2261 | @@ -61,9 +61,9 @@ |
2262 | defer ts.Close() |
2263 | |
2264 | === modified file 'http13client/request_test.go' |
2265 | ---- http13client/request_test.go 2014-03-19 23:13:58 +0000 |
2266 | -+++ http13client/request_test.go 2014-03-19 23:40:12 +0000 |
2267 | -@@ -164,11 +164,11 @@ |
2268 | +--- http13client/request_test.go 2014-06-20 12:46:25 +0000 |
2269 | ++++ http13client/request_test.go 2014-06-20 12:46:45 +0000 |
2270 | +@@ -182,11 +182,11 @@ |
2271 | switch r.URL.Path { |
2272 | case "/": |
2273 | w.Header().Set("Location", "/foo/") |
2274 | @@ -79,9 +79,9 @@ |
2275 | defer ts.Close() |
2276 | |
2277 | === modified file 'http13client/response.go' |
2278 | ---- http13client/response.go 2014-03-19 23:13:58 +0000 |
2279 | -+++ http13client/response.go 2014-03-19 23:38:56 +0000 |
2280 | -@@ -204,9 +204,8 @@ |
2281 | +--- http13client/response.go 2014-06-20 12:46:25 +0000 |
2282 | ++++ http13client/response.go 2014-06-20 12:46:45 +0000 |
2283 | +@@ -205,9 +205,8 @@ |
2284 | // Status line |
2285 | text := r.Status |
2286 | if text == "" { |
2287 | @@ -94,10 +94,23 @@ |
2288 | } |
2289 | } |
2290 | |
2291 | +=== modified file 'http13client/responsewrite_test.go' |
2292 | +--- http13client/responsewrite_test.go 2014-06-20 12:46:25 +0000 |
2293 | ++++ http13client/responsewrite_test.go 2014-06-20 12:47:05 +0000 |
2294 | +@@ -197,7 +197,7 @@ |
2295 | + // there were two. |
2296 | + { |
2297 | + Response{ |
2298 | +- StatusCode: StatusOK, |
2299 | ++ StatusCode: http.StatusOK, |
2300 | + ProtoMajor: 1, |
2301 | + ProtoMinor: 1, |
2302 | + Request: &Request{Method: "POST"}, |
2303 | + |
2304 | === modified file 'http13client/transport_test.go' |
2305 | ---- http13client/transport_test.go 2014-03-19 23:13:58 +0000 |
2306 | -+++ http13client/transport_test.go 2014-03-19 23:40:39 +0000 |
2307 | -@@ -968,7 +968,7 @@ |
2308 | +--- http13client/transport_test.go 2014-06-20 12:46:25 +0000 |
2309 | ++++ http13client/transport_test.go 2014-06-20 12:46:45 +0000 |
2310 | +@@ -1004,7 +1004,7 @@ |
2311 | defer afterTest(t) |
2312 | const deniedMsg = "sorry, denied." |
2313 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
2314 | @@ -106,7 +119,7 @@ |
2315 | })) |
2316 | defer ts.Close() |
2317 | tr := &Transport{} |
2318 | -@@ -992,7 +992,7 @@ |
2319 | +@@ -1028,7 +1028,7 @@ |
2320 | func TestChunkedNoContent(t *testing.T) { |
2321 | defer afterTest(t) |
2322 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
2323 | |
2324 | === modified file 'http13client/_patches/fix_tests.patch' |
2325 | --- http13client/_patches/fix_tests.patch 2014-03-19 23:13:58 +0000 |
2326 | +++ http13client/_patches/fix_tests.patch 2014-06-20 13:02:58 +0000 |
2327 | @@ -1,6 +1,6 @@ |
2328 | === modified file 'http13client/client_test.go' |
2329 | ---- http13client/client_test.go 2014-03-19 21:38:56 +0000 |
2330 | -+++ http13client/client_test.go 2014-03-19 22:27:37 +0000 |
2331 | +--- http13client/client_test.go 2014-06-20 11:00:47 +0000 |
2332 | ++++ http13client/client_test.go 2014-06-20 12:05:53 +0000 |
2333 | @@ -15,8 +15,8 @@ |
2334 | "fmt" |
2335 | "io" |
2336 | @@ -11,7 +11,7 @@ |
2337 | . "launchpad.net/ubuntu-push/http13client" |
2338 | "net/http/httptest" |
2339 | "net/url" |
2340 | -@@ -27,7 +27,7 @@ |
2341 | +@@ -29,7 +29,7 @@ |
2342 | "time" |
2343 | ) |
2344 | |
2345 | @@ -20,7 +20,7 @@ |
2346 | w.Header().Set("Last-Modified", "sometime") |
2347 | fmt.Fprintf(w, "User-agent: go\nDisallow: /something/") |
2348 | }) |
2349 | -@@ -193,7 +193,7 @@ |
2350 | +@@ -195,7 +195,7 @@ |
2351 | func TestClientRedirects(t *testing.T) { |
2352 | defer afterTest(t) |
2353 | var ts *httptest.Server |
2354 | @@ -29,7 +29,7 @@ |
2355 | n, _ := strconv.Atoi(r.FormValue("n")) |
2356 | // Test Referer header. (7 is arbitrary position to test at) |
2357 | if n == 7 { |
2358 | -@@ -202,7 +202,7 @@ |
2359 | +@@ -204,7 +204,7 @@ |
2360 | } |
2361 | } |
2362 | if n < 15 { |
2363 | @@ -38,7 +38,7 @@ |
2364 | return |
2365 | } |
2366 | fmt.Fprintf(w, "n=%d", n) |
2367 | -@@ -271,7 +271,7 @@ |
2368 | +@@ -273,7 +273,7 @@ |
2369 | bytes.Buffer |
2370 | } |
2371 | var ts *httptest.Server |
2372 | @@ -47,7 +47,7 @@ |
2373 | log.Lock() |
2374 | fmt.Fprintf(&log.Buffer, "%s %s ", r.Method, r.RequestURI) |
2375 | log.Unlock() |
2376 | -@@ -312,21 +312,21 @@ |
2377 | +@@ -314,21 +314,21 @@ |
2378 | } |
2379 | } |
2380 | |
2381 | @@ -75,7 +75,7 @@ |
2382 | w.Write([]byte("hello")) |
2383 | } |
2384 | }) |
2385 | -@@ -334,7 +334,7 @@ |
2386 | +@@ -336,7 +336,7 @@ |
2387 | func TestClientSendsCookieFromJar(t *testing.T) { |
2388 | tr := &recordingTransport{} |
2389 | client := &Client{Transport: tr} |
2390 | @@ -84,7 +84,7 @@ |
2391 | us := "http://dummy.faketld/" |
2392 | u, _ := url.Parse(us) |
2393 | client.Jar.SetCookies(u, expectedCookies) |
2394 | -@@ -364,19 +364,19 @@ |
2395 | +@@ -366,19 +366,19 @@ |
2396 | // scope of all cookies. |
2397 | type TestJar struct { |
2398 | m sync.Mutex |
2399 | @@ -108,7 +108,7 @@ |
2400 | j.m.Lock() |
2401 | defer j.m.Unlock() |
2402 | return j.perURL[u.Host] |
2403 | -@@ -391,7 +391,7 @@ |
2404 | +@@ -393,7 +393,7 @@ |
2405 | Jar: new(TestJar), |
2406 | } |
2407 | u, _ := url.Parse(ts.URL) |
2408 | @@ -117,7 +117,7 @@ |
2409 | resp, err := c.Get(ts.URL) |
2410 | if err != nil { |
2411 | t.Fatalf("Get: %v", err) |
2412 | -@@ -400,7 +400,7 @@ |
2413 | +@@ -402,7 +402,7 @@ |
2414 | matchReturnedCookies(t, expectedCookies, resp.Cookies()) |
2415 | } |
2416 | |
2417 | @@ -126,7 +126,7 @@ |
2418 | if len(given) != len(expected) { |
2419 | t.Logf("Received cookies: %v", given) |
2420 | t.Errorf("Expected %d cookies, got %d", len(expected), len(given)) |
2421 | -@@ -421,14 +421,14 @@ |
2422 | +@@ -423,14 +423,14 @@ |
2423 | |
2424 | func TestJarCalls(t *testing.T) { |
2425 | defer afterTest(t) |
2426 | @@ -144,7 +144,7 @@ |
2427 | } |
2428 | })) |
2429 | defer ts.Close() |
2430 | -@@ -468,11 +468,11 @@ |
2431 | +@@ -470,11 +470,11 @@ |
2432 | log bytes.Buffer |
2433 | } |
2434 | |
2435 | @@ -158,7 +158,7 @@ |
2436 | j.logf("Cookies(%q)\n", u) |
2437 | return nil |
2438 | } |
2439 | -@@ -486,11 +486,11 @@ |
2440 | +@@ -488,11 +488,11 @@ |
2441 | func TestStreamingGet(t *testing.T) { |
2442 | defer afterTest(t) |
2443 | say := make(chan string) |
2444 | @@ -173,7 +173,7 @@ |
2445 | } |
2446 | })) |
2447 | defer ts.Close() |
2448 | -@@ -536,7 +536,7 @@ |
2449 | +@@ -538,7 +538,7 @@ |
2450 | // don't send a TCP packet per line of the http request + body. |
2451 | func TestClientWrites(t *testing.T) { |
2452 | defer afterTest(t) |
2453 | @@ -182,7 +182,7 @@ |
2454 | })) |
2455 | defer ts.Close() |
2456 | |
2457 | -@@ -568,46 +568,6 @@ |
2458 | +@@ -570,46 +570,6 @@ |
2459 | } |
2460 | } |
2461 | |
2462 | @@ -229,7 +229,7 @@ |
2463 | func TestClientErrorWithRequestURI(t *testing.T) { |
2464 | defer afterTest(t) |
2465 | req, _ := NewRequest("GET", "http://localhost:1234/", nil) |
2466 | -@@ -639,7 +599,7 @@ |
2467 | +@@ -641,7 +601,7 @@ |
2468 | |
2469 | func TestClientWithCorrectTLSServerName(t *testing.T) { |
2470 | defer afterTest(t) |
2471 | @@ -238,7 +238,7 @@ |
2472 | if r.TLS.ServerName != "127.0.0.1" { |
2473 | t.Errorf("expected client to set ServerName 127.0.0.1, got: %q", r.TLS.ServerName) |
2474 | } |
2475 | -@@ -652,33 +612,6 @@ |
2476 | +@@ -654,33 +614,6 @@ |
2477 | } |
2478 | } |
2479 | |
2480 | @@ -272,7 +272,7 @@ |
2481 | // Test for golang.org/issue/5829; the Transport should respect TLSClientConfig.ServerName |
2482 | // when not empty. |
2483 | // |
2484 | -@@ -690,7 +623,7 @@ |
2485 | +@@ -692,7 +625,7 @@ |
2486 | // The httptest.Server has a cert with "example.com" as its name. |
2487 | func TestTransportUsesTLSConfigServerName(t *testing.T) { |
2488 | defer afterTest(t) |
2489 | @@ -281,7 +281,7 @@ |
2490 | w.Write([]byte("Hello")) |
2491 | })) |
2492 | defer ts.Close() |
2493 | -@@ -711,7 +644,7 @@ |
2494 | +@@ -713,7 +646,7 @@ |
2495 | |
2496 | func TestResponseSetsTLSConnectionState(t *testing.T) { |
2497 | defer afterTest(t) |
2498 | @@ -290,7 +290,7 @@ |
2499 | w.Write([]byte("Hello")) |
2500 | })) |
2501 | defer ts.Close() |
2502 | -@@ -739,7 +672,7 @@ |
2503 | +@@ -741,7 +674,7 @@ |
2504 | // Verify Response.ContentLength is populated. http://golang.org/issue/4126 |
2505 | func TestClientHeadContentLength(t *testing.T) { |
2506 | defer afterTest(t) |
2507 | @@ -299,7 +299,7 @@ |
2508 | if v := r.FormValue("cl"); v != "" { |
2509 | w.Header().Set("Content-Length", v) |
2510 | } |
2511 | -@@ -775,7 +708,7 @@ |
2512 | +@@ -777,7 +710,7 @@ |
2513 | func TestEmptyPasswordAuth(t *testing.T) { |
2514 | defer afterTest(t) |
2515 | gopher := "gopher" |
2516 | @@ -308,7 +308,7 @@ |
2517 | auth := r.Header.Get("Authorization") |
2518 | if strings.HasPrefix(auth, "Basic ") { |
2519 | encoded := auth[6:] |
2520 | -@@ -847,15 +780,15 @@ |
2521 | +@@ -849,15 +782,15 @@ |
2522 | defer afterTest(t) |
2523 | sawRoot := make(chan bool, 1) |
2524 | sawSlow := make(chan bool, 1) |
2525 | @@ -327,7 +327,7 @@ |
2526 | sawSlow <- true |
2527 | time.Sleep(2 * time.Second) |
2528 | return |
2529 | -@@ -908,10 +841,10 @@ |
2530 | +@@ -910,10 +843,10 @@ |
2531 | func TestClientRedirectEatsBody(t *testing.T) { |
2532 | defer afterTest(t) |
2533 | saw := make(chan string, 2) |
2534 | @@ -340,233 +340,50 @@ |
2535 | } |
2536 | })) |
2537 | defer ts.Close() |
2538 | - |
2539 | -=== modified file 'http13client/cookie_test.go' |
2540 | ---- http13client/cookie_test.go 2014-03-19 20:20:19 +0000 |
2541 | -+++ http13client/cookie_test.go 2014-03-19 22:27:37 +0000 |
2542 | -@@ -9,6 +9,7 @@ |
2543 | - "encoding/json" |
2544 | - "fmt" |
2545 | - "log" |
2546 | -+ "net/http" |
2547 | - "os" |
2548 | - "reflect" |
2549 | - "strings" |
2550 | -@@ -17,39 +18,39 @@ |
2551 | - ) |
2552 | - |
2553 | - var writeSetCookiesTests = []struct { |
2554 | -- Cookie *Cookie |
2555 | -+ Cookie *http.Cookie |
2556 | - Raw string |
2557 | - }{ |
2558 | - { |
2559 | -- &Cookie{Name: "cookie-1", Value: "v$1"}, |
2560 | -+ &http.Cookie{Name: "cookie-1", Value: "v$1"}, |
2561 | - "cookie-1=v$1", |
2562 | - }, |
2563 | - { |
2564 | -- &Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600}, |
2565 | -+ &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600}, |
2566 | - "cookie-2=two; Max-Age=3600", |
2567 | - }, |
2568 | - { |
2569 | -- &Cookie{Name: "cookie-3", Value: "three", Domain: ".example.com"}, |
2570 | -+ &http.Cookie{Name: "cookie-3", Value: "three", Domain: ".example.com"}, |
2571 | - "cookie-3=three; Domain=example.com", |
2572 | - }, |
2573 | - { |
2574 | -- &Cookie{Name: "cookie-4", Value: "four", Path: "/restricted/"}, |
2575 | -+ &http.Cookie{Name: "cookie-4", Value: "four", Path: "/restricted/"}, |
2576 | - "cookie-4=four; Path=/restricted/", |
2577 | - }, |
2578 | - { |
2579 | -- &Cookie{Name: "cookie-5", Value: "five", Domain: "wrong;bad.abc"}, |
2580 | -+ &http.Cookie{Name: "cookie-5", Value: "five", Domain: "wrong;bad.abc"}, |
2581 | - "cookie-5=five", |
2582 | - }, |
2583 | - { |
2584 | -- &Cookie{Name: "cookie-6", Value: "six", Domain: "bad-.abc"}, |
2585 | -+ &http.Cookie{Name: "cookie-6", Value: "six", Domain: "bad-.abc"}, |
2586 | - "cookie-6=six", |
2587 | - }, |
2588 | - { |
2589 | -- &Cookie{Name: "cookie-7", Value: "seven", Domain: "127.0.0.1"}, |
2590 | -+ &http.Cookie{Name: "cookie-7", Value: "seven", Domain: "127.0.0.1"}, |
2591 | - "cookie-7=seven; Domain=127.0.0.1", |
2592 | - }, |
2593 | - { |
2594 | -- &Cookie{Name: "cookie-8", Value: "eight", Domain: "::1"}, |
2595 | -+ &http.Cookie{Name: "cookie-8", Value: "eight", Domain: "::1"}, |
2596 | - "cookie-8=eight", |
2597 | - }, |
2598 | - } |
2599 | -@@ -71,10 +72,10 @@ |
2600 | - } |
2601 | - } |
2602 | - |
2603 | --type headerOnlyResponseWriter Header |
2604 | -+type headerOnlyResponseWriter http.Header |
2605 | - |
2606 | --func (ho headerOnlyResponseWriter) Header() Header { |
2607 | -- return Header(ho) |
2608 | -+func (ho headerOnlyResponseWriter) Header() http.Header { |
2609 | -+ return http.Header(ho) |
2610 | - } |
2611 | - |
2612 | - func (ho headerOnlyResponseWriter) Write([]byte) (int, error) { |
2613 | -@@ -86,9 +87,9 @@ |
2614 | - } |
2615 | - |
2616 | - func TestSetCookie(t *testing.T) { |
2617 | -- m := make(Header) |
2618 | -- SetCookie(headerOnlyResponseWriter(m), &Cookie{Name: "cookie-1", Value: "one", Path: "/restricted/"}) |
2619 | -- SetCookie(headerOnlyResponseWriter(m), &Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600}) |
2620 | -+ m := make(http.Header) |
2621 | -+ http.SetCookie(headerOnlyResponseWriter(m), &http.Cookie{Name: "cookie-1", Value: "one", Path: "/restricted/"}) |
2622 | -+ http.SetCookie(headerOnlyResponseWriter(m), &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600}) |
2623 | - if l := len(m["Set-Cookie"]); l != 2 { |
2624 | - t.Fatalf("expected %d cookies, got %d", 2, l) |
2625 | - } |
2626 | -@@ -101,19 +102,19 @@ |
2627 | - } |
2628 | - |
2629 | - var addCookieTests = []struct { |
2630 | -- Cookies []*Cookie |
2631 | -+ Cookies []*http.Cookie |
2632 | - Raw string |
2633 | - }{ |
2634 | - { |
2635 | -- []*Cookie{}, |
2636 | -+ []*http.Cookie{}, |
2637 | - "", |
2638 | - }, |
2639 | - { |
2640 | -- []*Cookie{{Name: "cookie-1", Value: "v$1"}}, |
2641 | -+ []*http.Cookie{{Name: "cookie-1", Value: "v$1"}}, |
2642 | - "cookie-1=v$1", |
2643 | - }, |
2644 | - { |
2645 | -- []*Cookie{ |
2646 | -+ []*http.Cookie{ |
2647 | - {Name: "cookie-1", Value: "v$1"}, |
2648 | - {Name: "cookie-2", Value: "v$2"}, |
2649 | - {Name: "cookie-3", Value: "v$3"}, |
2650 | -@@ -136,16 +137,16 @@ |
2651 | - } |
2652 | - |
2653 | - var readSetCookiesTests = []struct { |
2654 | -- Header Header |
2655 | -- Cookies []*Cookie |
2656 | -+ Header http.Header |
2657 | -+ Cookies []*http.Cookie |
2658 | - }{ |
2659 | - { |
2660 | -- Header{"Set-Cookie": {"Cookie-1=v$1"}}, |
2661 | -- []*Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}}, |
2662 | -+ http.Header{"Set-Cookie": {"Cookie-1=v$1"}}, |
2663 | -+ []*http.Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}}, |
2664 | - }, |
2665 | - { |
2666 | -- Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}}, |
2667 | -- []*Cookie{{ |
2668 | -+ http.Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}}, |
2669 | -+ []*http.Cookie{{ |
2670 | - Name: "NID", |
2671 | - Value: "99=YsDT5i3E-CXax-", |
2672 | - Path: "/", |
2673 | -@@ -157,8 +158,8 @@ |
2674 | - }}, |
2675 | - }, |
2676 | - { |
2677 | -- Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}}, |
2678 | -- []*Cookie{{ |
2679 | -+ http.Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}}, |
2680 | -+ []*http.Cookie{{ |
2681 | - Name: ".ASPXAUTH", |
2682 | - Value: "7E3AA", |
2683 | - Path: "/", |
2684 | -@@ -169,8 +170,8 @@ |
2685 | - }}, |
2686 | - }, |
2687 | - { |
2688 | -- Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}}, |
2689 | -- []*Cookie{{ |
2690 | -+ http.Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}}, |
2691 | -+ []*http.Cookie{{ |
2692 | - Name: "ASP.NET_SessionId", |
2693 | - Value: "foo", |
2694 | - Path: "/", |
2695 | -@@ -207,37 +208,37 @@ |
2696 | - } |
2697 | - |
2698 | - var readCookiesTests = []struct { |
2699 | -- Header Header |
2700 | -+ Header http.Header |
2701 | - Filter string |
2702 | -- Cookies []*Cookie |
2703 | -+ Cookies []*http.Cookie |
2704 | - }{ |
2705 | - { |
2706 | -- Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, |
2707 | -- "", |
2708 | -- []*Cookie{ |
2709 | -- {Name: "Cookie-1", Value: "v$1"}, |
2710 | -- {Name: "c2", Value: "v2"}, |
2711 | -- }, |
2712 | -- }, |
2713 | -- { |
2714 | -- Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, |
2715 | -- "c2", |
2716 | -- []*Cookie{ |
2717 | -- {Name: "c2", Value: "v2"}, |
2718 | -- }, |
2719 | -- }, |
2720 | -- { |
2721 | -- Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, |
2722 | -- "", |
2723 | -- []*Cookie{ |
2724 | -- {Name: "Cookie-1", Value: "v$1"}, |
2725 | -- {Name: "c2", Value: "v2"}, |
2726 | -- }, |
2727 | -- }, |
2728 | -- { |
2729 | -- Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, |
2730 | -- "c2", |
2731 | -- []*Cookie{ |
2732 | -+ http.Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, |
2733 | -+ "", |
2734 | -+ []*http.Cookie{ |
2735 | -+ {Name: "Cookie-1", Value: "v$1"}, |
2736 | -+ {Name: "c2", Value: "v2"}, |
2737 | -+ }, |
2738 | -+ }, |
2739 | -+ { |
2740 | -+ http.Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, |
2741 | -+ "c2", |
2742 | -+ []*http.Cookie{ |
2743 | -+ {Name: "c2", Value: "v2"}, |
2744 | -+ }, |
2745 | -+ }, |
2746 | -+ { |
2747 | -+ http.Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, |
2748 | -+ "", |
2749 | -+ []*http.Cookie{ |
2750 | -+ {Name: "Cookie-1", Value: "v$1"}, |
2751 | -+ {Name: "c2", Value: "v2"}, |
2752 | -+ }, |
2753 | -+ }, |
2754 | -+ { |
2755 | -+ http.Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, |
2756 | -+ "c2", |
2757 | -+ []*http.Cookie{ |
2758 | - {Name: "c2", Value: "v2"}, |
2759 | - }, |
2760 | - }, |
2761 | +@@ -957,7 +890,7 @@ |
2762 | + |
2763 | + func TestClientTrailers(t *testing.T) { |
2764 | + defer afterTest(t) |
2765 | +- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { |
2766 | ++ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
2767 | + w.Header().Set("Connection", "close") |
2768 | + w.Header().Set("Trailer", "Server-Trailer-A, Server-Trailer-B") |
2769 | + w.Header().Add("Trailer", "Server-Trailer-C") |
2770 | +@@ -992,9 +925,9 @@ |
2771 | + // trailers to be sent, if and only if they were |
2772 | + // previously declared with w.Header().Set("Trailer", |
2773 | + // ..keys..) |
2774 | +- w.(Flusher).Flush() |
2775 | +- conn, buf, _ := w.(Hijacker).Hijack() |
2776 | +- t := Header{} |
2777 | ++ w.(http.Flusher).Flush() |
2778 | ++ conn, buf, _ := w.(http.Hijacker).Hijack() |
2779 | ++ t := http.Header{} |
2780 | + t.Set("Server-Trailer-A", "valuea") |
2781 | + t.Set("Server-Trailer-C", "valuec") // skipping B |
2782 | + buf.WriteString("0\r\n") // eof |
2783 | +@@ -1015,7 +948,7 @@ |
2784 | + req.Trailer["Client-Trailer-B"] = []string{"valueb"} |
2785 | + }), |
2786 | + )) |
2787 | +- req.Trailer = Header{ |
2788 | ++ req.Trailer = http.Header{ |
2789 | + "Client-Trailer-A": nil, // to be set later |
2790 | + "Client-Trailer-B": nil, // to be set later |
2791 | + } |
2792 | +@@ -1027,7 +960,7 @@ |
2793 | + if err := wantBody(res, err, "decl: [Client-Trailer-A Client-Trailer-B], vals: valuea, valueb"); err != nil { |
2794 | + t.Error(err) |
2795 | + } |
2796 | +- want := Header{ |
2797 | ++ want := http.Header{ |
2798 | + "Server-Trailer-A": []string{"valuea"}, |
2799 | + "Server-Trailer-B": nil, |
2800 | + "Server-Trailer-C": []string{"valuec"}, |
2801 | |
2802 | === modified file 'http13client/export_test.go' |
2803 | ---- http13client/export_test.go 2014-03-19 20:20:19 +0000 |
2804 | -+++ http13client/export_test.go 2014-03-19 22:27:37 +0000 |
2805 | +--- http13client/export_test.go 2014-06-20 11:00:47 +0000 |
2806 | ++++ http13client/export_test.go 2014-06-20 12:00:22 +0000 |
2807 | @@ -9,15 +9,12 @@ |
2808 | |
2809 | import ( |
2810 | @@ -599,8 +416,8 @@ |
2811 | noProxyEnv.reset() |
2812 | |
2813 | === modified file 'http13client/header_test.go' |
2814 | ---- http13client/header_test.go 2014-03-19 20:20:19 +0000 |
2815 | -+++ http13client/header_test.go 2014-03-19 22:27:37 +0000 |
2816 | +--- http13client/header_test.go 2014-06-20 11:00:47 +0000 |
2817 | ++++ http13client/header_test.go 2014-06-20 12:00:22 +0000 |
2818 | @@ -6,19 +6,20 @@ |
2819 | |
2820 | import ( |
2821 | @@ -731,8 +548,8 @@ |
2822 | } |
2823 | |
2824 | === modified file 'http13client/npn_test.go' |
2825 | ---- http13client/npn_test.go 2014-03-19 21:38:56 +0000 |
2826 | -+++ http13client/npn_test.go 2014-03-19 22:27:37 +0000 |
2827 | +--- http13client/npn_test.go 2014-06-20 11:00:47 +0000 |
2828 | ++++ http13client/npn_test.go 2014-06-20 12:05:53 +0000 |
2829 | @@ -11,13 +11,14 @@ |
2830 | "io" |
2831 | "io/ioutil" |
2832 | @@ -792,8 +609,8 @@ |
2833 | func (w http09Writer) WriteHeader(int) {} // no headers |
2834 | |
2835 | === modified file 'http13client/readrequest_test.go' |
2836 | ---- http13client/readrequest_test.go 2014-03-19 20:20:19 +0000 |
2837 | -+++ http13client/readrequest_test.go 2014-03-19 22:27:37 +0000 |
2838 | +--- http13client/readrequest_test.go 2014-06-20 11:00:47 +0000 |
2839 | ++++ http13client/readrequest_test.go 2014-06-20 12:00:22 +0000 |
2840 | @@ -9,6 +9,7 @@ |
2841 | "bytes" |
2842 | "fmt" |
2843 | @@ -909,8 +726,8 @@ |
2844 | Close: false, |
2845 | |
2846 | === modified file 'http13client/request_test.go' |
2847 | ---- http13client/request_test.go 2014-03-19 21:38:56 +0000 |
2848 | -+++ http13client/request_test.go 2014-03-19 22:27:37 +0000 |
2849 | +--- http13client/request_test.go 2014-06-20 11:00:47 +0000 |
2850 | ++++ http13client/request_test.go 2014-06-20 12:05:53 +0000 |
2851 | @@ -12,6 +12,7 @@ |
2852 | "io/ioutil" |
2853 | "mime/multipart" |
2854 | @@ -945,8 +762,26 @@ |
2855 | + req.Header = http.Header{"Content-Type": {"text/plain"}} |
2856 | multipart, err = req.MultipartReader() |
2857 | if multipart != nil { |
2858 | - t.Errorf("unexpected multipart for text/plain") |
2859 | -@@ -159,7 +160,7 @@ |
2860 | + t.Error("unexpected multipart for text/plain") |
2861 | +@@ -161,7 +162,7 @@ |
2862 | + func TestParseMultipartForm(t *testing.T) { |
2863 | + req := &Request{ |
2864 | + Method: "POST", |
2865 | +- Header: Header{"Content-Type": {`multipart/form-data; boundary="foo123"`}}, |
2866 | ++ Header: http.Header{"Content-Type": {`multipart/form-data; boundary="foo123"`}}, |
2867 | + Body: ioutil.NopCloser(new(bytes.Buffer)), |
2868 | + } |
2869 | + err := req.ParseMultipartForm(25) |
2870 | +@@ -169,7 +170,7 @@ |
2871 | + t.Error("expected multipart EOF, got nil") |
2872 | + } |
2873 | + |
2874 | +- req.Header = Header{"Content-Type": {"text/plain"}} |
2875 | ++ req.Header = http.Header{"Content-Type": {"text/plain"}} |
2876 | + err = req.ParseMultipartForm(25) |
2877 | + if err != ErrNotMultipart { |
2878 | + t.Error("expected ErrNotMultipart for text/plain") |
2879 | +@@ -177,7 +178,7 @@ |
2880 | } |
2881 | |
2882 | func TestRedirect(t *testing.T) { |
2883 | @@ -957,8 +792,8 @@ |
2884 | w.Header().Set("Location", "/foo/") |
2885 | |
2886 | === modified file 'http13client/requestwrite_test.go' |
2887 | ---- http13client/requestwrite_test.go 2014-03-19 20:20:19 +0000 |
2888 | -+++ http13client/requestwrite_test.go 2014-03-19 22:27:37 +0000 |
2889 | +--- http13client/requestwrite_test.go 2014-06-20 11:00:47 +0000 |
2890 | ++++ http13client/requestwrite_test.go 2014-06-20 12:00:22 +0000 |
2891 | @@ -10,6 +10,7 @@ |
2892 | "fmt" |
2893 | "io" |
2894 | @@ -1068,8 +903,8 @@ |
2895 | var braw bytes.Buffer |
2896 | |
2897 | === modified file 'http13client/response_test.go' |
2898 | ---- http13client/response_test.go 2014-03-19 20:20:19 +0000 |
2899 | -+++ http13client/response_test.go 2014-03-19 22:27:37 +0000 |
2900 | +--- http13client/response_test.go 2014-06-20 11:00:47 +0000 |
2901 | ++++ http13client/response_test.go 2014-06-20 12:05:53 +0000 |
2902 | @@ -12,6 +12,7 @@ |
2903 | "fmt" |
2904 | "io" |
2905 | @@ -1078,7 +913,7 @@ |
2906 | "net/url" |
2907 | "reflect" |
2908 | "regexp" |
2909 | -@@ -44,7 +45,7 @@ |
2910 | +@@ -48,7 +49,7 @@ |
2911 | ProtoMajor: 1, |
2912 | ProtoMinor: 0, |
2913 | Request: dummyReq("GET"), |
2914 | @@ -1087,7 +922,7 @@ |
2915 | "Connection": {"close"}, // TODO(rsc): Delete? |
2916 | }, |
2917 | Close: true, |
2918 | -@@ -67,7 +68,7 @@ |
2919 | +@@ -71,7 +72,7 @@ |
2920 | Proto: "HTTP/1.1", |
2921 | ProtoMajor: 1, |
2922 | ProtoMinor: 1, |
2923 | @@ -1096,7 +931,7 @@ |
2924 | Request: dummyReq("GET"), |
2925 | Close: true, |
2926 | ContentLength: -1, |
2927 | -@@ -88,7 +89,7 @@ |
2928 | +@@ -92,7 +93,7 @@ |
2929 | Proto: "HTTP/1.1", |
2930 | ProtoMajor: 1, |
2931 | ProtoMinor: 1, |
2932 | @@ -1105,7 +940,7 @@ |
2933 | Request: dummyReq("GET"), |
2934 | Close: false, |
2935 | ContentLength: 0, |
2936 | -@@ -112,7 +113,7 @@ |
2937 | +@@ -116,7 +117,7 @@ |
2938 | ProtoMajor: 1, |
2939 | ProtoMinor: 0, |
2940 | Request: dummyReq("GET"), |
2941 | @@ -1114,61 +949,61 @@ |
2942 | "Connection": {"close"}, |
2943 | "Content-Length": {"10"}, |
2944 | }, |
2945 | -@@ -142,7 +143,7 @@ |
2946 | - ProtoMajor: 1, |
2947 | - ProtoMinor: 1, |
2948 | - Request: dummyReq("GET"), |
2949 | -- Header: Header{}, |
2950 | -+ Header: http.Header{}, |
2951 | - Close: false, |
2952 | - ContentLength: -1, |
2953 | - TransferEncoding: []string{"chunked"}, |
2954 | -@@ -169,7 +170,7 @@ |
2955 | - ProtoMajor: 1, |
2956 | - ProtoMinor: 1, |
2957 | - Request: dummyReq("GET"), |
2958 | -- Header: Header{}, |
2959 | -+ Header: http.Header{}, |
2960 | - Close: false, |
2961 | - ContentLength: -1, |
2962 | - TransferEncoding: []string{"chunked"}, |
2963 | -@@ -191,7 +192,7 @@ |
2964 | - ProtoMajor: 1, |
2965 | - ProtoMinor: 1, |
2966 | - Request: dummyReq("HEAD"), |
2967 | -- Header: Header{}, |
2968 | -+ Header: http.Header{}, |
2969 | - TransferEncoding: []string{"chunked"}, |
2970 | - Close: false, |
2971 | - ContentLength: -1, |
2972 | -@@ -213,7 +214,7 @@ |
2973 | - ProtoMajor: 1, |
2974 | - ProtoMinor: 0, |
2975 | - Request: dummyReq("HEAD"), |
2976 | -- Header: Header{"Content-Length": {"256"}}, |
2977 | -+ Header: http.Header{"Content-Length": {"256"}}, |
2978 | - TransferEncoding: nil, |
2979 | - Close: true, |
2980 | - ContentLength: 256, |
2981 | -@@ -235,7 +236,7 @@ |
2982 | - ProtoMajor: 1, |
2983 | - ProtoMinor: 1, |
2984 | - Request: dummyReq("HEAD"), |
2985 | -- Header: Header{"Content-Length": {"256"}}, |
2986 | -+ Header: http.Header{"Content-Length": {"256"}}, |
2987 | - TransferEncoding: nil, |
2988 | - Close: false, |
2989 | - ContentLength: 256, |
2990 | -@@ -256,7 +257,7 @@ |
2991 | - ProtoMajor: 1, |
2992 | - ProtoMinor: 0, |
2993 | - Request: dummyReq("HEAD"), |
2994 | -- Header: Header{}, |
2995 | -+ Header: http.Header{}, |
2996 | - TransferEncoding: nil, |
2997 | - Close: true, |
2998 | - ContentLength: -1, |
2999 | -@@ -278,7 +279,7 @@ |
3000 | +@@ -146,7 +147,7 @@ |
3001 | + ProtoMajor: 1, |
3002 | + ProtoMinor: 1, |
3003 | + Request: dummyReq("GET"), |
3004 | +- Header: Header{}, |
3005 | ++ Header: http.Header{}, |
3006 | + Close: false, |
3007 | + ContentLength: -1, |
3008 | + TransferEncoding: []string{"chunked"}, |
3009 | +@@ -173,7 +174,7 @@ |
3010 | + ProtoMajor: 1, |
3011 | + ProtoMinor: 1, |
3012 | + Request: dummyReq("GET"), |
3013 | +- Header: Header{}, |
3014 | ++ Header: http.Header{}, |
3015 | + Close: false, |
3016 | + ContentLength: -1, |
3017 | + TransferEncoding: []string{"chunked"}, |
3018 | +@@ -195,7 +196,7 @@ |
3019 | + ProtoMajor: 1, |
3020 | + ProtoMinor: 1, |
3021 | + Request: dummyReq("HEAD"), |
3022 | +- Header: Header{}, |
3023 | ++ Header: http.Header{}, |
3024 | + TransferEncoding: []string{"chunked"}, |
3025 | + Close: false, |
3026 | + ContentLength: -1, |
3027 | +@@ -217,7 +218,7 @@ |
3028 | + ProtoMajor: 1, |
3029 | + ProtoMinor: 0, |
3030 | + Request: dummyReq("HEAD"), |
3031 | +- Header: Header{"Content-Length": {"256"}}, |
3032 | ++ Header: http.Header{"Content-Length": {"256"}}, |
3033 | + TransferEncoding: nil, |
3034 | + Close: true, |
3035 | + ContentLength: 256, |
3036 | +@@ -239,7 +240,7 @@ |
3037 | + ProtoMajor: 1, |
3038 | + ProtoMinor: 1, |
3039 | + Request: dummyReq("HEAD"), |
3040 | +- Header: Header{"Content-Length": {"256"}}, |
3041 | ++ Header: http.Header{"Content-Length": {"256"}}, |
3042 | + TransferEncoding: nil, |
3043 | + Close: false, |
3044 | + ContentLength: 256, |
3045 | +@@ -260,7 +261,7 @@ |
3046 | + ProtoMajor: 1, |
3047 | + ProtoMinor: 0, |
3048 | + Request: dummyReq("HEAD"), |
3049 | +- Header: Header{}, |
3050 | ++ Header: http.Header{}, |
3051 | + TransferEncoding: nil, |
3052 | + Close: true, |
3053 | + ContentLength: -1, |
3054 | +@@ -282,7 +283,7 @@ |
3055 | ProtoMajor: 1, |
3056 | ProtoMinor: 1, |
3057 | Request: dummyReq("GET"), |
3058 | @@ -1177,25 +1012,25 @@ |
3059 | "Content-Length": {"0"}, |
3060 | }, |
3061 | Close: false, |
3062 | -@@ -299,7 +300,7 @@ |
3063 | - ProtoMajor: 1, |
3064 | - ProtoMinor: 0, |
3065 | - Request: dummyReq("GET"), |
3066 | -- Header: Header{}, |
3067 | -+ Header: http.Header{}, |
3068 | - Close: true, |
3069 | - ContentLength: -1, |
3070 | - }, |
3071 | -@@ -318,7 +319,7 @@ |
3072 | - ProtoMajor: 1, |
3073 | - ProtoMinor: 0, |
3074 | - Request: dummyReq("GET"), |
3075 | -- Header: Header{}, |
3076 | -+ Header: http.Header{}, |
3077 | - Close: true, |
3078 | - ContentLength: -1, |
3079 | - }, |
3080 | -@@ -340,7 +341,7 @@ |
3081 | +@@ -303,7 +304,7 @@ |
3082 | + ProtoMajor: 1, |
3083 | + ProtoMinor: 0, |
3084 | + Request: dummyReq("GET"), |
3085 | +- Header: Header{}, |
3086 | ++ Header: http.Header{}, |
3087 | + Close: true, |
3088 | + ContentLength: -1, |
3089 | + }, |
3090 | +@@ -322,7 +323,7 @@ |
3091 | + ProtoMajor: 1, |
3092 | + ProtoMinor: 0, |
3093 | + Request: dummyReq("GET"), |
3094 | +- Header: Header{}, |
3095 | ++ Header: http.Header{}, |
3096 | + Close: true, |
3097 | + ContentLength: -1, |
3098 | + }, |
3099 | +@@ -344,7 +345,7 @@ |
3100 | ProtoMajor: 1, |
3101 | ProtoMinor: 1, |
3102 | Request: dummyReq("GET"), |
3103 | @@ -1204,7 +1039,7 @@ |
3104 | "Content-Type": []string{"multipart/byteranges; boundary=18a75608c8f47cef"}, |
3105 | }, |
3106 | Close: true, |
3107 | -@@ -363,7 +364,7 @@ |
3108 | +@@ -367,7 +368,7 @@ |
3109 | Proto: "HTTP/1.0", |
3110 | ProtoMajor: 1, |
3111 | ProtoMinor: 0, |
3112 | @@ -1213,7 +1048,7 @@ |
3113 | "Connection": {"close"}, // TODO(rsc): Delete? |
3114 | }, |
3115 | Close: true, |
3116 | -@@ -545,7 +546,7 @@ |
3117 | +@@ -549,7 +550,7 @@ |
3118 | func TestLocationResponse(t *testing.T) { |
3119 | for i, tt := range responseLocationTests { |
3120 | res := new(Response) |
3121 | @@ -1222,7 +1057,7 @@ |
3122 | res.Header.Set("Location", tt.location) |
3123 | if tt.requrl != "" { |
3124 | res.Request = &Request{} |
3125 | -@@ -626,16 +627,3 @@ |
3126 | +@@ -630,16 +631,3 @@ |
3127 | t.Errorf("ReadResponse = %v; want io.ErrUnexpectedEOF", err) |
3128 | } |
3129 | } |
3130 | @@ -1241,8 +1076,8 @@ |
3131 | -} |
3132 | |
3133 | === modified file 'http13client/responsewrite_test.go' |
3134 | ---- http13client/responsewrite_test.go 2014-03-19 20:20:19 +0000 |
3135 | -+++ http13client/responsewrite_test.go 2014-03-19 22:27:37 +0000 |
3136 | +--- http13client/responsewrite_test.go 2014-06-20 11:00:47 +0000 |
3137 | ++++ http13client/responsewrite_test.go 2014-06-20 12:05:53 +0000 |
3138 | @@ -7,6 +7,7 @@ |
3139 | import ( |
3140 | "bytes" |
3141 | @@ -1257,7 +1092,7 @@ |
3142 | Request: dummyReq("GET"), |
3143 | - Header: Header{}, |
3144 | + Header: http.Header{}, |
3145 | - Body: ioutil.NopCloser(bytes.NewBufferString("abcdef")), |
3146 | + Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
3147 | ContentLength: 6, |
3148 | }, |
3149 | @@ -41,7 +42,7 @@ |
3150 | @@ -1270,6 +1105,60 @@ |
3151 | ContentLength: -1, |
3152 | }, |
3153 | @@ -56,7 +57,7 @@ |
3154 | + ProtoMajor: 1, |
3155 | + ProtoMinor: 1, |
3156 | + Request: dummyReq("GET"), |
3157 | +- Header: Header{}, |
3158 | ++ Header: http.Header{}, |
3159 | + Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
3160 | + ContentLength: -1, |
3161 | + Close: true, |
3162 | +@@ -73,7 +74,7 @@ |
3163 | + ProtoMajor: 1, |
3164 | + ProtoMinor: 1, |
3165 | + Request: dummyReq11("GET"), |
3166 | +- Header: Header{}, |
3167 | ++ Header: http.Header{}, |
3168 | + Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
3169 | + ContentLength: -1, |
3170 | + Close: false, |
3171 | +@@ -91,7 +92,7 @@ |
3172 | + ProtoMajor: 1, |
3173 | + ProtoMinor: 1, |
3174 | + Request: dummyReq11("GET"), |
3175 | +- Header: Header{}, |
3176 | ++ Header: http.Header{}, |
3177 | + Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
3178 | + ContentLength: -1, |
3179 | + TransferEncoding: []string{"chunked"}, |
3180 | +@@ -108,7 +109,7 @@ |
3181 | + ProtoMajor: 1, |
3182 | + ProtoMinor: 1, |
3183 | + Request: dummyReq11("GET"), |
3184 | +- Header: Header{}, |
3185 | ++ Header: http.Header{}, |
3186 | + Body: nil, |
3187 | + ContentLength: 0, |
3188 | + Close: false, |
3189 | +@@ -124,7 +125,7 @@ |
3190 | + ProtoMajor: 1, |
3191 | + ProtoMinor: 1, |
3192 | + Request: dummyReq11("GET"), |
3193 | +- Header: Header{}, |
3194 | ++ Header: http.Header{}, |
3195 | + Body: ioutil.NopCloser(strings.NewReader("")), |
3196 | + ContentLength: 0, |
3197 | + Close: false, |
3198 | +@@ -140,7 +141,7 @@ |
3199 | + ProtoMajor: 1, |
3200 | + ProtoMinor: 1, |
3201 | + Request: dummyReq11("GET"), |
3202 | +- Header: Header{}, |
3203 | ++ Header: http.Header{}, |
3204 | + Body: ioutil.NopCloser(strings.NewReader("foo")), |
3205 | + ContentLength: 0, |
3206 | + Close: false, |
3207 | +@@ -156,7 +157,7 @@ |
3208 | ProtoMajor: 1, |
3209 | ProtoMinor: 1, |
3210 | Request: dummyReq("GET"), |
3211 | @@ -1278,7 +1167,7 @@ |
3212 | Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
3213 | ContentLength: 6, |
3214 | TransferEncoding: []string{"chunked"}, |
3215 | -@@ -77,7 +78,7 @@ |
3216 | +@@ -177,7 +178,7 @@ |
3217 | ProtoMajor: 1, |
3218 | ProtoMinor: 1, |
3219 | Request: dummyReq("GET"), |
3220 | @@ -1287,11 +1176,20 @@ |
3221 | "Foo": []string{" Bar\nBaz "}, |
3222 | }, |
3223 | Body: nil, |
3224 | +@@ -200,7 +201,7 @@ |
3225 | + ProtoMajor: 1, |
3226 | + ProtoMinor: 1, |
3227 | + Request: &Request{Method: "POST"}, |
3228 | +- Header: Header{}, |
3229 | ++ Header: http.Header{}, |
3230 | + ContentLength: 0, |
3231 | + TransferEncoding: nil, |
3232 | + Body: nil, |
3233 | |
3234 | === modified file 'http13client/transport_test.go' |
3235 | ---- http13client/transport_test.go 2014-03-19 21:38:56 +0000 |
3236 | -+++ http13client/transport_test.go 2014-03-19 22:27:37 +0000 |
3237 | -@@ -17,8 +17,8 @@ |
3238 | +--- http13client/transport_test.go 2014-06-20 11:00:47 +0000 |
3239 | ++++ http13client/transport_test.go 2014-06-20 12:05:53 +0000 |
3240 | +@@ -18,8 +18,8 @@ |
3241 | "io/ioutil" |
3242 | "log" |
3243 | "net" |
3244 | @@ -1301,7 +1199,7 @@ |
3245 | "net/http/httptest" |
3246 | "net/url" |
3247 | "os" |
3248 | -@@ -34,7 +34,7 @@ |
3249 | +@@ -35,7 +35,7 @@ |
3250 | // and then verify that the final 2 responses get errors back. |
3251 | |
3252 | // hostPortHandler writes back the client's "host:port". |
3253 | @@ -1310,7 +1208,7 @@ |
3254 | if r.FormValue("close") == "true" { |
3255 | w.Header().Set("Connection", "close") |
3256 | } |
3257 | -@@ -280,7 +280,7 @@ |
3258 | +@@ -289,7 +289,7 @@ |
3259 | const msg = "foobar" |
3260 | |
3261 | var addrSeen map[string]int |
3262 | @@ -1319,7 +1217,7 @@ |
3263 | addrSeen[r.RemoteAddr]++ |
3264 | if r.URL.Path == "/chunked/" { |
3265 | w.WriteHeader(200) |
3266 | -@@ -299,7 +299,7 @@ |
3267 | +@@ -308,7 +308,7 @@ |
3268 | wantLen := []int{len(msg), -1}[pi] |
3269 | addrSeen = make(map[string]int) |
3270 | for i := 0; i < 3; i++ { |
3271 | @@ -1328,7 +1226,7 @@ |
3272 | if err != nil { |
3273 | t.Errorf("Get %s: %v", path, err) |
3274 | continue |
3275 | -@@ -329,7 +329,7 @@ |
3276 | +@@ -338,7 +338,7 @@ |
3277 | defer afterTest(t) |
3278 | resch := make(chan string) |
3279 | gotReq := make(chan bool) |
3280 | @@ -1337,7 +1235,7 @@ |
3281 | gotReq <- true |
3282 | msg := <-resch |
3283 | _, err := w.Write([]byte(msg)) |
3284 | -@@ -457,12 +457,12 @@ |
3285 | +@@ -466,12 +466,12 @@ |
3286 | if testing.Short() { |
3287 | t.Skip("skipping test in short mode") |
3288 | } |
3289 | @@ -1353,7 +1251,7 @@ |
3290 | buf.Flush() |
3291 | conn.Close() |
3292 | })) |
3293 | -@@ -510,7 +510,7 @@ |
3294 | +@@ -519,7 +519,7 @@ |
3295 | // with no bodies properly |
3296 | func TestTransportHeadResponses(t *testing.T) { |
3297 | defer afterTest(t) |
3298 | @@ -1362,7 +1260,7 @@ |
3299 | if r.Method != "HEAD" { |
3300 | panic("expected HEAD; got " + r.Method) |
3301 | } |
3302 | -@@ -545,7 +545,7 @@ |
3303 | +@@ -554,7 +554,7 @@ |
3304 | // on responses to HEAD requests. |
3305 | func TestTransportHeadChunkedResponse(t *testing.T) { |
3306 | defer afterTest(t) |
3307 | @@ -1371,7 +1269,7 @@ |
3308 | if r.Method != "HEAD" { |
3309 | panic("expected HEAD; got " + r.Method) |
3310 | } |
3311 | -@@ -588,7 +588,7 @@ |
3312 | +@@ -597,7 +597,7 @@ |
3313 | func TestRoundTripGzip(t *testing.T) { |
3314 | defer afterTest(t) |
3315 | const responseBody = "test response body" |
3316 | @@ -1380,7 +1278,7 @@ |
3317 | accept := req.Header.Get("Accept-Encoding") |
3318 | if expect := req.FormValue("expect_accept"); accept != expect { |
3319 | t.Errorf("in handler, test %v: Accept-Encoding = %q, want %q", |
3320 | -@@ -647,7 +647,7 @@ |
3321 | +@@ -656,7 +656,7 @@ |
3322 | defer afterTest(t) |
3323 | const testString = "The test string aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" |
3324 | const nRandBytes = 1024 * 1024 |
3325 | @@ -1389,7 +1287,7 @@ |
3326 | if req.Method == "HEAD" { |
3327 | if g := req.Header.Get("Accept-Encoding"); g != "" { |
3328 | t.Errorf("HEAD request sent with Accept-Encoding of %q; want none", g) |
3329 | -@@ -742,11 +742,11 @@ |
3330 | +@@ -751,11 +751,11 @@ |
3331 | func TestTransportProxy(t *testing.T) { |
3332 | defer afterTest(t) |
3333 | ch := make(chan string, 1) |
3334 | @@ -1403,7 +1301,7 @@ |
3335 | ch <- "proxy for " + r.URL.String() |
3336 | })) |
3337 | defer proxy.Close() |
3338 | -@@ -770,7 +770,7 @@ |
3339 | +@@ -779,7 +779,7 @@ |
3340 | // Content-Encoding is removed. |
3341 | func TestTransportGzipRecursive(t *testing.T) { |
3342 | defer afterTest(t) |
3343 | @@ -1412,7 +1310,16 @@ |
3344 | w.Header().Set("Content-Encoding", "gzip") |
3345 | w.Write(rgz) |
3346 | })) |
3347 | -@@ -802,7 +802,7 @@ |
3348 | +@@ -807,7 +807,7 @@ |
3349 | + // a short gzip body |
3350 | + func TestTransportGzipShort(t *testing.T) { |
3351 | + defer afterTest(t) |
3352 | +- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { |
3353 | ++ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
3354 | + w.Header().Set("Content-Encoding", "gzip") |
3355 | + w.Write([]byte{0x1f, 0x8b}) |
3356 | + })) |
3357 | +@@ -838,7 +838,7 @@ |
3358 | defer afterTest(t) |
3359 | gotReqCh := make(chan bool) |
3360 | unblockCh := make(chan bool) |
3361 | @@ -1421,7 +1328,7 @@ |
3362 | gotReqCh <- true |
3363 | <-unblockCh |
3364 | w.Header().Set("Content-Length", "0") |
3365 | -@@ -869,7 +869,7 @@ |
3366 | +@@ -905,7 +905,7 @@ |
3367 | t.Skip("skipping test; see http://golang.org/issue/7237") |
3368 | } |
3369 | defer afterTest(t) |
3370 | @@ -1430,7 +1337,7 @@ |
3371 | })) |
3372 | defer ts.Close() |
3373 | |
3374 | -@@ -912,7 +912,7 @@ |
3375 | +@@ -948,7 +948,7 @@ |
3376 | c := &Client{Transport: tr} |
3377 | |
3378 | unblockCh := make(chan bool, 1) |
3379 | @@ -1439,7 +1346,7 @@ |
3380 | <-unblockCh |
3381 | tr.CloseIdleConnections() |
3382 | })) |
3383 | -@@ -939,7 +939,7 @@ |
3384 | +@@ -975,7 +975,7 @@ |
3385 | func TestIssue3644(t *testing.T) { |
3386 | defer afterTest(t) |
3387 | const numFoos = 5000 |
3388 | @@ -1448,7 +1355,7 @@ |
3389 | w.Header().Set("Connection", "close") |
3390 | for i := 0; i < numFoos; i++ { |
3391 | w.Write([]byte("foo ")) |
3392 | -@@ -967,8 +967,8 @@ |
3393 | +@@ -1003,8 +1003,8 @@ |
3394 | func TestIssue3595(t *testing.T) { |
3395 | defer afterTest(t) |
3396 | const deniedMsg = "sorry, denied." |
3397 | @@ -1459,7 +1366,7 @@ |
3398 | })) |
3399 | defer ts.Close() |
3400 | tr := &Transport{} |
3401 | -@@ -991,7 +991,7 @@ |
3402 | +@@ -1027,7 +1027,7 @@ |
3403 | // "client fails to handle requests with no body and chunked encoding" |
3404 | func TestChunkedNoContent(t *testing.T) { |
3405 | defer afterTest(t) |
3406 | @@ -1468,7 +1375,7 @@ |
3407 | w.WriteHeader(StatusNoContent) |
3408 | })) |
3409 | defer ts.Close() |
3410 | -@@ -1019,7 +1019,7 @@ |
3411 | +@@ -1055,7 +1055,7 @@ |
3412 | maxProcs, numReqs = 4, 50 |
3413 | } |
3414 | defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(maxProcs)) |
3415 | @@ -1477,7 +1384,7 @@ |
3416 | fmt.Fprintf(w, "%v", r.FormValue("echo")) |
3417 | })) |
3418 | defer ts.Close() |
3419 | -@@ -1080,8 +1080,8 @@ |
3420 | +@@ -1116,8 +1116,8 @@ |
3421 | } |
3422 | defer afterTest(t) |
3423 | const debug = false |
3424 | @@ -1488,7 +1395,7 @@ |
3425 | io.Copy(w, neverEnding('a')) |
3426 | }) |
3427 | ts := httptest.NewServer(mux) |
3428 | -@@ -1144,11 +1144,11 @@ |
3429 | +@@ -1180,11 +1180,11 @@ |
3430 | } |
3431 | defer afterTest(t) |
3432 | const debug = false |
3433 | @@ -1503,20 +1410,22 @@ |
3434 | defer r.Body.Close() |
3435 | io.Copy(ioutil.Discard, r.Body) |
3436 | }) |
3437 | -@@ -1214,9 +1214,9 @@ |
3438 | - if testing.Short() { |
3439 | +@@ -1251,11 +1251,11 @@ |
3440 | t.Skip("skipping timeout test in -short mode") |
3441 | } |
3442 | + inHandler := make(chan bool, 1) |
3443 | - mux := NewServeMux() |
3444 | -- mux.HandleFunc("/fast", func(w ResponseWriter, r *Request) {}) |
3445 | +- mux.HandleFunc("/fast", func(w ResponseWriter, r *Request) { |
3446 | ++ mux := http.NewServeMux() |
3447 | ++ mux.HandleFunc("/fast", func(w http.ResponseWriter, r *http.Request) { |
3448 | + inHandler <- true |
3449 | + }) |
3450 | - mux.HandleFunc("/slow", func(w ResponseWriter, r *Request) { |
3451 | -+ mux := http.NewServeMux() |
3452 | -+ mux.HandleFunc("/fast", func(w http.ResponseWriter, r *http.Request) {}) |
3453 | + mux.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) { |
3454 | + inHandler <- true |
3455 | time.Sleep(2 * time.Second) |
3456 | }) |
3457 | - ts := httptest.NewServer(mux) |
3458 | -@@ -1276,9 +1276,9 @@ |
3459 | +@@ -1322,9 +1322,9 @@ |
3460 | t.Skip("skipping test in -short mode") |
3461 | } |
3462 | unblockc := make(chan bool) |
3463 | @@ -1528,7 +1437,7 @@ |
3464 | <-unblockc |
3465 | })) |
3466 | defer ts.Close() |
3467 | -@@ -1386,14 +1386,14 @@ |
3468 | +@@ -1431,14 +1431,14 @@ |
3469 | defer afterTest(t) |
3470 | writeErr := make(chan error, 1) |
3471 | msg := []byte("young\n") |
3472 | @@ -1545,7 +1454,7 @@ |
3473 | } |
3474 | })) |
3475 | defer ts.Close() |
3476 | -@@ -1449,7 +1449,7 @@ |
3477 | +@@ -1494,7 +1494,7 @@ |
3478 | res := &Response{ |
3479 | Status: "200 OK", |
3480 | StatusCode: 200, |
3481 | @@ -1554,7 +1463,7 @@ |
3482 | Body: ioutil.NopCloser(strings.NewReader("You wanted " + req.URL.String())), |
3483 | } |
3484 | return res, nil |
3485 | -@@ -1478,7 +1478,7 @@ |
3486 | +@@ -1523,7 +1523,7 @@ |
3487 | defer afterTest(t) |
3488 | tr := &Transport{} |
3489 | _, err := tr.RoundTrip(&Request{ |
3490 | @@ -1563,7 +1472,7 @@ |
3491 | URL: &url.URL{ |
3492 | Scheme: "http", |
3493 | }, |
3494 | -@@ -1492,14 +1492,14 @@ |
3495 | +@@ -1537,14 +1537,14 @@ |
3496 | func TestTransportSocketLateBinding(t *testing.T) { |
3497 | defer afterTest(t) |
3498 | |
3499 | @@ -1582,7 +1491,7 @@ |
3500 | w.Header().Set("bar-ipport", r.RemoteAddr) |
3501 | }) |
3502 | ts := httptest.NewServer(mux) |
3503 | -@@ -1720,7 +1720,7 @@ |
3504 | +@@ -1767,7 +1767,7 @@ |
3505 | var mu sync.Mutex |
3506 | var n int |
3507 | |
3508 | @@ -1591,7 +1500,7 @@ |
3509 | mu.Lock() |
3510 | n++ |
3511 | mu.Unlock() |
3512 | -@@ -1756,7 +1756,7 @@ |
3513 | +@@ -1803,7 +1803,7 @@ |
3514 | // then closes it. |
3515 | func TestTransportClosesRequestBody(t *testing.T) { |
3516 | defer afterTest(t) |
3517 | @@ -1600,4 +1509,49 @@ |
3518 | io.Copy(ioutil.Discard, r.Body) |
3519 | })) |
3520 | defer ts.Close() |
3521 | +@@ -1890,9 +1890,9 @@ |
3522 | + t.Skip("skipping flaky test on Windows; golang.org/issue/7634") |
3523 | + } |
3524 | + closedc := make(chan bool, 1) |
3525 | +- ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) { |
3526 | ++ ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
3527 | + if strings.Contains(r.URL.Path, "/keep-alive-then-die") { |
3528 | +- conn, _, _ := w.(Hijacker).Hijack() |
3529 | ++ conn, _, _ := w.(http.Hijacker).Hijack() |
3530 | + conn.Write([]byte("HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nfoo")) |
3531 | + conn.Close() |
3532 | + closedc <- true |
3533 | +@@ -1994,12 +1994,12 @@ |
3534 | + } |
3535 | + defer closeConn() |
3536 | + |
3537 | +- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { |
3538 | ++ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
3539 | + if r.Method == "GET" { |
3540 | + io.WriteString(w, "bar") |
3541 | + return |
3542 | + } |
3543 | +- conn, _, _ := w.(Hijacker).Hijack() |
3544 | ++ conn, _, _ := w.(http.Hijacker).Hijack() |
3545 | + sconn.Lock() |
3546 | + sconn.c = conn |
3547 | + sconn.Unlock() |
3548 | +@@ -2056,7 +2056,7 @@ |
3549 | + } |
3550 | + defer afterTest(t) |
3551 | + readBody := make(chan error, 1) |
3552 | +- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { |
3553 | ++ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
3554 | + _, err := ioutil.ReadAll(r.Body) |
3555 | + readBody <- err |
3556 | + })) |
3557 | +@@ -2098,7 +2098,7 @@ |
3558 | + } |
3559 | + } |
3560 | + |
3561 | +-func wantBody(res *http.Response, err error, want string) error { |
3562 | ++func wantBody(res *Response, err error, want string) error { |
3563 | + if err != nil { |
3564 | + return err |
3565 | + } |
3566 | |
3567 | |
3568 | === modified file 'http13client/_using.txt' |
3569 | --- http13client/_using.txt 2014-03-20 12:20:01 +0000 |
3570 | +++ http13client/_using.txt 2014-06-20 13:02:58 +0000 |
3571 | @@ -1,5 +1,5 @@ |
3572 | -parent: 19512:32c32aef2a41 tip |
3573 | - test: enable bug385_32 test on amd64p32. |
3574 | -branch: default |
3575 | +parent: 20169:9895f9e36435 go1.3 release |
3576 | + go1.3 |
3577 | +branch: release-branch.go1.3 |
3578 | commit: (clean) |
3579 | update: (current) |
3580 | |
3581 | === modified file 'http13client/client.go' |
3582 | --- http13client/client.go 2014-03-20 09:26:28 +0000 |
3583 | +++ http13client/client.go 2014-06-20 13:02:58 +0000 |
3584 | @@ -92,8 +92,9 @@ |
3585 | // authentication, or cookies. |
3586 | // |
3587 | // RoundTrip should not modify the request, except for |
3588 | - // consuming and closing the Body. The request's URL and |
3589 | - // Header fields are guaranteed to be initialized. |
3590 | + // consuming and closing the Body, including on errors. The |
3591 | + // request's URL and Header fields are guaranteed to be |
3592 | + // initialized. |
3593 | RoundTrip(*Request) (*Response, error) |
3594 | } |
3595 | |
3596 | @@ -141,6 +142,9 @@ |
3597 | // (typically Transport) may not be able to re-use a persistent TCP |
3598 | // connection to the server for a subsequent "keep-alive" request. |
3599 | // |
3600 | +// The request Body, if non-nil, will be closed by the underlying |
3601 | +// Transport, even on errors. |
3602 | +// |
3603 | // Generally Get, Post, or PostForm will be used instead of Do. |
3604 | func (c *Client) Do(req *Request) (resp *Response, err error) { |
3605 | if req.Method == "GET" || req.Method == "HEAD" { |
3606 | @@ -163,14 +167,17 @@ |
3607 | // Caller should close resp.Body when done reading from it. |
3608 | func send(req *Request, t RoundTripper) (resp *Response, err error) { |
3609 | if t == nil { |
3610 | + req.closeBody() |
3611 | return nil, errors.New("http: no Client.Transport or DefaultTransport") |
3612 | } |
3613 | |
3614 | if req.URL == nil { |
3615 | + req.closeBody() |
3616 | return nil, errors.New("http: nil Request.URL") |
3617 | } |
3618 | |
3619 | if req.RequestURI != "" { |
3620 | + req.closeBody() |
3621 | return nil, errors.New("http: Request.RequestURI can't be set in client requests.") |
3622 | } |
3623 | |
3624 | @@ -278,6 +285,7 @@ |
3625 | var via []*Request |
3626 | |
3627 | if ireq.URL == nil { |
3628 | + ireq.closeBody() |
3629 | return nil, errors.New("http: nil Request.URL") |
3630 | } |
3631 | |
3632 | @@ -400,7 +408,7 @@ |
3633 | // Caller should close resp.Body when done reading from it. |
3634 | // |
3635 | // If the provided body is also an io.Closer, it is closed after the |
3636 | -// body is successfully written to the server. |
3637 | +// request. |
3638 | func (c *Client) Post(url string, bodyType string, body io.Reader) (resp *Response, err error) { |
3639 | req, err := NewRequest("POST", url, body) |
3640 | if err != nil { |
3641 | |
3642 | === modified file 'http13client/client_test.go' |
3643 | --- http13client/client_test.go 2014-03-20 09:26:28 +0000 |
3644 | +++ http13client/client_test.go 2014-06-20 13:02:58 +0000 |
3645 | @@ -20,6 +20,8 @@ |
3646 | "net/http" |
3647 | "net/http/httptest" |
3648 | "net/url" |
3649 | + "reflect" |
3650 | + "sort" |
3651 | "strconv" |
3652 | "strings" |
3653 | "sync" |
3654 | @@ -877,3 +879,93 @@ |
3655 | t.Fatal("server saw different client ports before & after the redirect") |
3656 | } |
3657 | } |
3658 | + |
3659 | +// eofReaderFunc is an io.Reader that runs itself, and then returns io.EOF. |
3660 | +type eofReaderFunc func() |
3661 | + |
3662 | +func (f eofReaderFunc) Read(p []byte) (n int, err error) { |
3663 | + f() |
3664 | + return 0, io.EOF |
3665 | +} |
3666 | + |
3667 | +func TestClientTrailers(t *testing.T) { |
3668 | + defer afterTest(t) |
3669 | + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
3670 | + w.Header().Set("Connection", "close") |
3671 | + w.Header().Set("Trailer", "Server-Trailer-A, Server-Trailer-B") |
3672 | + w.Header().Add("Trailer", "Server-Trailer-C") |
3673 | + |
3674 | + var decl []string |
3675 | + for k := range r.Trailer { |
3676 | + decl = append(decl, k) |
3677 | + } |
3678 | + sort.Strings(decl) |
3679 | + |
3680 | + slurp, err := ioutil.ReadAll(r.Body) |
3681 | + if err != nil { |
3682 | + t.Errorf("Server reading request body: %v", err) |
3683 | + } |
3684 | + if string(slurp) != "foo" { |
3685 | + t.Errorf("Server read request body %q; want foo", slurp) |
3686 | + } |
3687 | + if r.Trailer == nil { |
3688 | + io.WriteString(w, "nil Trailer") |
3689 | + } else { |
3690 | + fmt.Fprintf(w, "decl: %v, vals: %s, %s", |
3691 | + decl, |
3692 | + r.Trailer.Get("Client-Trailer-A"), |
3693 | + r.Trailer.Get("Client-Trailer-B")) |
3694 | + } |
3695 | + |
3696 | + // TODO: golang.org/issue/7759: there's no way yet for |
3697 | + // the server to set trailers without hijacking, so do |
3698 | + // that for now, just to test the client. Later, in |
3699 | + // Go 1.4, it should be implicit that any mutations |
3700 | + // to w.Header() after the initial write are the |
3701 | + // trailers to be sent, if and only if they were |
3702 | + // previously declared with w.Header().Set("Trailer", |
3703 | + // ..keys..) |
3704 | + w.(http.Flusher).Flush() |
3705 | + conn, buf, _ := w.(http.Hijacker).Hijack() |
3706 | + t := http.Header{} |
3707 | + t.Set("Server-Trailer-A", "valuea") |
3708 | + t.Set("Server-Trailer-C", "valuec") // skipping B |
3709 | + buf.WriteString("0\r\n") // eof |
3710 | + t.Write(buf) |
3711 | + buf.WriteString("\r\n") // end of trailers |
3712 | + buf.Flush() |
3713 | + conn.Close() |
3714 | + })) |
3715 | + defer ts.Close() |
3716 | + |
3717 | + var req *Request |
3718 | + req, _ = NewRequest("POST", ts.URL, io.MultiReader( |
3719 | + eofReaderFunc(func() { |
3720 | + req.Trailer["Client-Trailer-A"] = []string{"valuea"} |
3721 | + }), |
3722 | + strings.NewReader("foo"), |
3723 | + eofReaderFunc(func() { |
3724 | + req.Trailer["Client-Trailer-B"] = []string{"valueb"} |
3725 | + }), |
3726 | + )) |
3727 | + req.Trailer = http.Header{ |
3728 | + "Client-Trailer-A": nil, // to be set later |
3729 | + "Client-Trailer-B": nil, // to be set later |
3730 | + } |
3731 | + req.ContentLength = -1 |
3732 | + res, err := DefaultClient.Do(req) |
3733 | + if err != nil { |
3734 | + t.Fatal(err) |
3735 | + } |
3736 | + if err := wantBody(res, err, "decl: [Client-Trailer-A Client-Trailer-B], vals: valuea, valueb"); err != nil { |
3737 | + t.Error(err) |
3738 | + } |
3739 | + want := http.Header{ |
3740 | + "Server-Trailer-A": []string{"valuea"}, |
3741 | + "Server-Trailer-B": nil, |
3742 | + "Server-Trailer-C": []string{"valuec"}, |
3743 | + } |
3744 | + if !reflect.DeepEqual(res.Trailer, want) { |
3745 | + t.Errorf("Response trailers = %#v; want %#v", res.Trailer, want) |
3746 | + } |
3747 | +} |
3748 | |
3749 | === modified file 'http13client/cookie.go' |
3750 | --- http13client/cookie.go 2014-03-19 23:13:58 +0000 |
3751 | +++ http13client/cookie.go 2014-06-20 13:02:58 +0000 |
3752 | @@ -55,11 +55,7 @@ |
3753 | attr, val = attr[:j], attr[j+1:] |
3754 | } |
3755 | lowerAttr := strings.ToLower(attr) |
3756 | - parseCookieValueFn := parseCookieValue |
3757 | - if lowerAttr == "expires" { |
3758 | - parseCookieValueFn = parseCookieExpiresValue |
3759 | - } |
3760 | - val, success = parseCookieValueFn(val) |
3761 | + val, success = parseCookieValue(val) |
3762 | if !success { |
3763 | c.Unparsed = append(c.Unparsed, parts[i]) |
3764 | continue |
3765 | @@ -230,12 +226,23 @@ |
3766 | // ; US-ASCII characters excluding CTLs, |
3767 | // ; whitespace DQUOTE, comma, semicolon, |
3768 | // ; and backslash |
3769 | +// We loosen this as spaces and commas are common in cookie values |
3770 | +// but we produce a quoted cookie-value in when value starts or ends |
3771 | +// with a comma or space. |
3772 | +// See http://golang.org/issue/7243 for the discussion. |
3773 | func sanitizeCookieValue(v string) string { |
3774 | - return sanitizeOrWarn("Cookie.Value", validCookieValueByte, v) |
3775 | + v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v) |
3776 | + if len(v) == 0 { |
3777 | + return v |
3778 | + } |
3779 | + if v[0] == ' ' || v[0] == ',' || v[len(v)-1] == ' ' || v[len(v)-1] == ',' { |
3780 | + return `"` + v + `"` |
3781 | + } |
3782 | + return v |
3783 | } |
3784 | |
3785 | func validCookieValueByte(b byte) bool { |
3786 | - return 0x20 < b && b < 0x7f && b != '"' && b != ',' && b != ';' && b != '\\' |
3787 | + return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\' |
3788 | } |
3789 | |
3790 | // path-av = "Path=" path-value |
3791 | @@ -270,38 +277,13 @@ |
3792 | return string(buf) |
3793 | } |
3794 | |
3795 | -func unquoteCookieValue(v string) string { |
3796 | - if len(v) > 1 && v[0] == '"' && v[len(v)-1] == '"' { |
3797 | - return v[1 : len(v)-1] |
3798 | - } |
3799 | - return v |
3800 | -} |
3801 | - |
3802 | -func isCookieByte(c byte) bool { |
3803 | - switch { |
3804 | - case c == 0x21, 0x23 <= c && c <= 0x2b, 0x2d <= c && c <= 0x3a, |
3805 | - 0x3c <= c && c <= 0x5b, 0x5d <= c && c <= 0x7e: |
3806 | - return true |
3807 | - } |
3808 | - return false |
3809 | -} |
3810 | - |
3811 | -func isCookieExpiresByte(c byte) (ok bool) { |
3812 | - return isCookieByte(c) || c == ',' || c == ' ' |
3813 | -} |
3814 | - |
3815 | func parseCookieValue(raw string) (string, bool) { |
3816 | - return parseCookieValueUsing(raw, isCookieByte) |
3817 | -} |
3818 | - |
3819 | -func parseCookieExpiresValue(raw string) (string, bool) { |
3820 | - return parseCookieValueUsing(raw, isCookieExpiresByte) |
3821 | -} |
3822 | - |
3823 | -func parseCookieValueUsing(raw string, validByte func(byte) bool) (string, bool) { |
3824 | - raw = unquoteCookieValue(raw) |
3825 | + // Strip the quotes, if present. |
3826 | + if len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' { |
3827 | + raw = raw[1 : len(raw)-1] |
3828 | + } |
3829 | for i := 0; i < len(raw); i++ { |
3830 | - if !validByte(raw[i]) { |
3831 | + if !validCookieValueByte(raw[i]) { |
3832 | return "", false |
3833 | } |
3834 | } |
3835 | |
3836 | === removed file 'http13client/cookie_test.go' |
3837 | --- http13client/cookie_test.go 2014-03-19 23:13:58 +0000 |
3838 | +++ http13client/cookie_test.go 1970-01-01 00:00:00 +0000 |
3839 | @@ -1,304 +0,0 @@ |
3840 | -// Copyright 2010 The Go Authors. All rights reserved. |
3841 | -// Use of this source code is governed by a BSD-style |
3842 | -// license that can be found in the LICENSE file. |
3843 | - |
3844 | -package http |
3845 | - |
3846 | -import ( |
3847 | - "bytes" |
3848 | - "encoding/json" |
3849 | - "fmt" |
3850 | - "log" |
3851 | - "net/http" |
3852 | - "os" |
3853 | - "reflect" |
3854 | - "strings" |
3855 | - "testing" |
3856 | - "time" |
3857 | -) |
3858 | - |
3859 | -var writeSetCookiesTests = []struct { |
3860 | - Cookie *http.Cookie |
3861 | - Raw string |
3862 | -}{ |
3863 | - { |
3864 | - &http.Cookie{Name: "cookie-1", Value: "v$1"}, |
3865 | - "cookie-1=v$1", |
3866 | - }, |
3867 | - { |
3868 | - &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600}, |
3869 | - "cookie-2=two; Max-Age=3600", |
3870 | - }, |
3871 | - { |
3872 | - &http.Cookie{Name: "cookie-3", Value: "three", Domain: ".example.com"}, |
3873 | - "cookie-3=three; Domain=example.com", |
3874 | - }, |
3875 | - { |
3876 | - &http.Cookie{Name: "cookie-4", Value: "four", Path: "/restricted/"}, |
3877 | - "cookie-4=four; Path=/restricted/", |
3878 | - }, |
3879 | - { |
3880 | - &http.Cookie{Name: "cookie-5", Value: "five", Domain: "wrong;bad.abc"}, |
3881 | - "cookie-5=five", |
3882 | - }, |
3883 | - { |
3884 | - &http.Cookie{Name: "cookie-6", Value: "six", Domain: "bad-.abc"}, |
3885 | - "cookie-6=six", |
3886 | - }, |
3887 | - { |
3888 | - &http.Cookie{Name: "cookie-7", Value: "seven", Domain: "127.0.0.1"}, |
3889 | - "cookie-7=seven; Domain=127.0.0.1", |
3890 | - }, |
3891 | - { |
3892 | - &http.Cookie{Name: "cookie-8", Value: "eight", Domain: "::1"}, |
3893 | - "cookie-8=eight", |
3894 | - }, |
3895 | -} |
3896 | - |
3897 | -func TestWriteSetCookies(t *testing.T) { |
3898 | - defer log.SetOutput(os.Stderr) |
3899 | - var logbuf bytes.Buffer |
3900 | - log.SetOutput(&logbuf) |
3901 | - |
3902 | - for i, tt := range writeSetCookiesTests { |
3903 | - if g, e := tt.Cookie.String(), tt.Raw; g != e { |
3904 | - t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, e, g) |
3905 | - continue |
3906 | - } |
3907 | - } |
3908 | - |
3909 | - if got, sub := logbuf.String(), "dropping domain attribute"; !strings.Contains(got, sub) { |
3910 | - t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got) |
3911 | - } |
3912 | -} |
3913 | - |
3914 | -type headerOnlyResponseWriter http.Header |
3915 | - |
3916 | -func (ho headerOnlyResponseWriter) Header() http.Header { |
3917 | - return http.Header(ho) |
3918 | -} |
3919 | - |
3920 | -func (ho headerOnlyResponseWriter) Write([]byte) (int, error) { |
3921 | - panic("NOIMPL") |
3922 | -} |
3923 | - |
3924 | -func (ho headerOnlyResponseWriter) WriteHeader(int) { |
3925 | - panic("NOIMPL") |
3926 | -} |
3927 | - |
3928 | -func TestSetCookie(t *testing.T) { |
3929 | - m := make(http.Header) |
3930 | - http.SetCookie(headerOnlyResponseWriter(m), &http.Cookie{Name: "cookie-1", Value: "one", Path: "/restricted/"}) |
3931 | - http.SetCookie(headerOnlyResponseWriter(m), &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600}) |
3932 | - if l := len(m["Set-Cookie"]); l != 2 { |
3933 | - t.Fatalf("expected %d cookies, got %d", 2, l) |
3934 | - } |
3935 | - if g, e := m["Set-Cookie"][0], "cookie-1=one; Path=/restricted/"; g != e { |
3936 | - t.Errorf("cookie #1: want %q, got %q", e, g) |
3937 | - } |
3938 | - if g, e := m["Set-Cookie"][1], "cookie-2=two; Max-Age=3600"; g != e { |
3939 | - t.Errorf("cookie #2: want %q, got %q", e, g) |
3940 | - } |
3941 | -} |
3942 | - |
3943 | -var addCookieTests = []struct { |
3944 | - Cookies []*http.Cookie |
3945 | - Raw string |
3946 | -}{ |
3947 | - { |
3948 | - []*http.Cookie{}, |
3949 | - "", |
3950 | - }, |
3951 | - { |
3952 | - []*http.Cookie{{Name: "cookie-1", Value: "v$1"}}, |
3953 | - "cookie-1=v$1", |
3954 | - }, |
3955 | - { |
3956 | - []*http.Cookie{ |
3957 | - {Name: "cookie-1", Value: "v$1"}, |
3958 | - {Name: "cookie-2", Value: "v$2"}, |
3959 | - {Name: "cookie-3", Value: "v$3"}, |
3960 | - }, |
3961 | - "cookie-1=v$1; cookie-2=v$2; cookie-3=v$3", |
3962 | - }, |
3963 | -} |
3964 | - |
3965 | -func TestAddCookie(t *testing.T) { |
3966 | - for i, tt := range addCookieTests { |
3967 | - req, _ := NewRequest("GET", "http://example.com/", nil) |
3968 | - for _, c := range tt.Cookies { |
3969 | - req.AddCookie(c) |
3970 | - } |
3971 | - if g := req.Header.Get("Cookie"); g != tt.Raw { |
3972 | - t.Errorf("Test %d:\nwant: %s\n got: %s\n", i, tt.Raw, g) |
3973 | - continue |
3974 | - } |
3975 | - } |
3976 | -} |
3977 | - |
3978 | -var readSetCookiesTests = []struct { |
3979 | - Header http.Header |
3980 | - Cookies []*http.Cookie |
3981 | -}{ |
3982 | - { |
3983 | - http.Header{"Set-Cookie": {"Cookie-1=v$1"}}, |
3984 | - []*http.Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}}, |
3985 | - }, |
3986 | - { |
3987 | - http.Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}}, |
3988 | - []*http.Cookie{{ |
3989 | - Name: "NID", |
3990 | - Value: "99=YsDT5i3E-CXax-", |
3991 | - Path: "/", |
3992 | - Domain: ".google.ch", |
3993 | - HttpOnly: true, |
3994 | - Expires: time.Date(2011, 11, 23, 1, 5, 3, 0, time.UTC), |
3995 | - RawExpires: "Wed, 23-Nov-2011 01:05:03 GMT", |
3996 | - Raw: "NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly", |
3997 | - }}, |
3998 | - }, |
3999 | - { |
4000 | - http.Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}}, |
4001 | - []*http.Cookie{{ |
4002 | - Name: ".ASPXAUTH", |
4003 | - Value: "7E3AA", |
4004 | - Path: "/", |
4005 | - Expires: time.Date(2012, 3, 7, 14, 25, 6, 0, time.UTC), |
4006 | - RawExpires: "Wed, 07-Mar-2012 14:25:06 GMT", |
4007 | - HttpOnly: true, |
4008 | - Raw: ".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly", |
4009 | - }}, |
4010 | - }, |
4011 | - { |
4012 | - http.Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}}, |
4013 | - []*http.Cookie{{ |
4014 | - Name: "ASP.NET_SessionId", |
4015 | - Value: "foo", |
4016 | - Path: "/", |
4017 | - HttpOnly: true, |
4018 | - Raw: "ASP.NET_SessionId=foo; path=/; HttpOnly", |
4019 | - }}, |
4020 | - }, |
4021 | - |
4022 | - // TODO(bradfitz): users have reported seeing this in the |
4023 | - // wild, but do browsers handle it? RFC 6265 just says "don't |
4024 | - // do that" (section 3) and then never mentions header folding |
4025 | - // again. |
4026 | - // Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly, .ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}}, |
4027 | -} |
4028 | - |
4029 | -func toJSON(v interface{}) string { |
4030 | - b, err := json.Marshal(v) |
4031 | - if err != nil { |
4032 | - return fmt.Sprintf("%#v", v) |
4033 | - } |
4034 | - return string(b) |
4035 | -} |
4036 | - |
4037 | -func TestReadSetCookies(t *testing.T) { |
4038 | - for i, tt := range readSetCookiesTests { |
4039 | - for n := 0; n < 2; n++ { // to verify readSetCookies doesn't mutate its input |
4040 | - c := readSetCookies(tt.Header) |
4041 | - if !reflect.DeepEqual(c, tt.Cookies) { |
4042 | - t.Errorf("#%d readSetCookies: have\n%s\nwant\n%s\n", i, toJSON(c), toJSON(tt.Cookies)) |
4043 | - continue |
4044 | - } |
4045 | - } |
4046 | - } |
4047 | -} |
4048 | - |
4049 | -var readCookiesTests = []struct { |
4050 | - Header http.Header |
4051 | - Filter string |
4052 | - Cookies []*http.Cookie |
4053 | -}{ |
4054 | - { |
4055 | - http.Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, |
4056 | - "", |
4057 | - []*http.Cookie{ |
4058 | - {Name: "Cookie-1", Value: "v$1"}, |
4059 | - {Name: "c2", Value: "v2"}, |
4060 | - }, |
4061 | - }, |
4062 | - { |
4063 | - http.Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, |
4064 | - "c2", |
4065 | - []*http.Cookie{ |
4066 | - {Name: "c2", Value: "v2"}, |
4067 | - }, |
4068 | - }, |
4069 | - { |
4070 | - http.Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, |
4071 | - "", |
4072 | - []*http.Cookie{ |
4073 | - {Name: "Cookie-1", Value: "v$1"}, |
4074 | - {Name: "c2", Value: "v2"}, |
4075 | - }, |
4076 | - }, |
4077 | - { |
4078 | - http.Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, |
4079 | - "c2", |
4080 | - []*http.Cookie{ |
4081 | - {Name: "c2", Value: "v2"}, |
4082 | - }, |
4083 | - }, |
4084 | -} |
4085 | - |
4086 | -func TestReadCookies(t *testing.T) { |
4087 | - for i, tt := range readCookiesTests { |
4088 | - for n := 0; n < 2; n++ { // to verify readCookies doesn't mutate its input |
4089 | - c := readCookies(tt.Header, tt.Filter) |
4090 | - if !reflect.DeepEqual(c, tt.Cookies) { |
4091 | - t.Errorf("#%d readCookies:\nhave: %s\nwant: %s\n", i, toJSON(c), toJSON(tt.Cookies)) |
4092 | - continue |
4093 | - } |
4094 | - } |
4095 | - } |
4096 | -} |
4097 | - |
4098 | -func TestCookieSanitizeValue(t *testing.T) { |
4099 | - defer log.SetOutput(os.Stderr) |
4100 | - var logbuf bytes.Buffer |
4101 | - log.SetOutput(&logbuf) |
4102 | - |
4103 | - tests := []struct { |
4104 | - in, want string |
4105 | - }{ |
4106 | - {"foo", "foo"}, |
4107 | - {"foo bar", "foobar"}, |
4108 | - {"\x00\x7e\x7f\x80", "\x7e"}, |
4109 | - {`"withquotes"`, "withquotes"}, |
4110 | - } |
4111 | - for _, tt := range tests { |
4112 | - if got := sanitizeCookieValue(tt.in); got != tt.want { |
4113 | - t.Errorf("sanitizeCookieValue(%q) = %q; want %q", tt.in, got, tt.want) |
4114 | - } |
4115 | - } |
4116 | - |
4117 | - if got, sub := logbuf.String(), "dropping invalid bytes"; !strings.Contains(got, sub) { |
4118 | - t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got) |
4119 | - } |
4120 | -} |
4121 | - |
4122 | -func TestCookieSanitizePath(t *testing.T) { |
4123 | - defer log.SetOutput(os.Stderr) |
4124 | - var logbuf bytes.Buffer |
4125 | - log.SetOutput(&logbuf) |
4126 | - |
4127 | - tests := []struct { |
4128 | - in, want string |
4129 | - }{ |
4130 | - {"/path", "/path"}, |
4131 | - {"/path with space/", "/path with space/"}, |
4132 | - {"/just;no;semicolon\x00orstuff/", "/justnosemicolonorstuff/"}, |
4133 | - } |
4134 | - for _, tt := range tests { |
4135 | - if got := sanitizeCookiePath(tt.in); got != tt.want { |
4136 | - t.Errorf("sanitizeCookiePath(%q) = %q; want %q", tt.in, got, tt.want) |
4137 | - } |
4138 | - } |
4139 | - |
4140 | - if got, sub := logbuf.String(), "dropping invalid bytes"; !strings.Contains(got, sub) { |
4141 | - t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got) |
4142 | - } |
4143 | -} |
4144 | |
4145 | === modified file 'http13client/request.go' |
4146 | --- http13client/request.go 2014-03-19 23:13:58 +0000 |
4147 | +++ http13client/request.go 2014-06-20 13:02:58 +0000 |
4148 | @@ -69,18 +69,31 @@ |
4149 | |
4150 | // A Request represents an HTTP request received by a server |
4151 | // or to be sent by a client. |
4152 | +// |
4153 | +// The field semantics differ slightly between client and server |
4154 | +// usage. In addition to the notes on the fields below, see the |
4155 | +// documentation for Request.Write and RoundTripper. |
4156 | type Request struct { |
4157 | - Method string // GET, POST, PUT, etc. |
4158 | + // Method specifies the HTTP method (GET, POST, PUT, etc.). |
4159 | + // For client requests an empty string means GET. |
4160 | + Method string |
4161 | |
4162 | - // URL is created from the URI supplied on the Request-Line |
4163 | - // as stored in RequestURI. |
4164 | - // |
4165 | - // For most requests, fields other than Path and RawQuery |
4166 | - // will be empty. (See RFC 2616, Section 5.1.2) |
4167 | + // URL specifies either the URI being requested (for server |
4168 | + // requests) or the URL to access (for client requests). |
4169 | + // |
4170 | + // For server requests the URL is parsed from the URI |
4171 | + // supplied on the Request-Line as stored in RequestURI. For |
4172 | + // most requests, fields other than Path and RawQuery will be |
4173 | + // empty. (See RFC 2616, Section 5.1.2) |
4174 | + // |
4175 | + // For client requests, the URL's Host specifies the server to |
4176 | + // connect to, while the Request's Host field optionally |
4177 | + // specifies the Host header value to send in the HTTP |
4178 | + // request. |
4179 | URL *url.URL |
4180 | |
4181 | // The protocol version for incoming requests. |
4182 | - // Outgoing requests always use HTTP/1.1. |
4183 | + // Client requests always use HTTP/1.1. |
4184 | Proto string // "HTTP/1.0" |
4185 | ProtoMajor int // 1 |
4186 | ProtoMinor int // 0 |
4187 | @@ -104,15 +117,20 @@ |
4188 | // The request parser implements this by canonicalizing the |
4189 | // name, making the first character and any characters |
4190 | // following a hyphen uppercase and the rest lowercase. |
4191 | + // |
4192 | + // For client requests certain headers are automatically |
4193 | + // added and may override values in Header. |
4194 | + // |
4195 | + // See the documentation for the Request.Write method. |
4196 | Header http.Header |
4197 | |
4198 | // Body is the request's body. |
4199 | // |
4200 | - // For client requests, a nil body means the request has no |
4201 | + // For client requests a nil body means the request has no |
4202 | // body, such as a GET request. The HTTP Client's Transport |
4203 | // is responsible for calling the Close method. |
4204 | // |
4205 | - // For server requests, the Request Body is always non-nil |
4206 | + // For server requests the Request Body is always non-nil |
4207 | // but will return EOF immediately when no body is present. |
4208 | // The Server will close the request body. The ServeHTTP |
4209 | // Handler does not need to. |
4210 | @@ -122,7 +140,7 @@ |
4211 | // The value -1 indicates that the length is unknown. |
4212 | // Values >= 0 indicate that the given number of bytes may |
4213 | // be read from Body. |
4214 | - // For outgoing requests, a value of 0 means unknown if Body is not nil. |
4215 | + // For client requests, a value of 0 means unknown if Body is not nil. |
4216 | ContentLength int64 |
4217 | |
4218 | // TransferEncoding lists the transfer encodings from outermost to |
4219 | @@ -133,13 +151,18 @@ |
4220 | TransferEncoding []string |
4221 | |
4222 | // Close indicates whether to close the connection after |
4223 | - // replying to this request. |
4224 | + // replying to this request (for servers) or after sending |
4225 | + // the request (for clients). |
4226 | Close bool |
4227 | |
4228 | - // The host on which the URL is sought. |
4229 | - // Per RFC 2616, this is either the value of the Host: header |
4230 | - // or the host name given in the URL itself. |
4231 | + // For server requests Host specifies the host on which the |
4232 | + // URL is sought. Per RFC 2616, this is either the value of |
4233 | + // the "Host" header or the host name given in the URL itself. |
4234 | // It may be of the form "host:port". |
4235 | + // |
4236 | + // For client requests Host optionally overrides the Host |
4237 | + // header to send. If empty, the Request.Write method uses |
4238 | + // the value of URL.Host. |
4239 | Host string |
4240 | |
4241 | // Form contains the parsed form data, including both the URL |
4242 | @@ -159,12 +182,24 @@ |
4243 | // The HTTP client ignores MultipartForm and uses Body instead. |
4244 | MultipartForm *multipart.Form |
4245 | |
4246 | - // Trailer maps trailer keys to values. Like for Header, if the |
4247 | - // response has multiple trailer lines with the same key, they will be |
4248 | - // concatenated, delimited by commas. |
4249 | - // For server requests, Trailer is only populated after Body has been |
4250 | - // closed or fully consumed. |
4251 | - // Trailer support is only partially complete. |
4252 | + // Trailer specifies additional headers that are sent after the request |
4253 | + // body. |
4254 | + // |
4255 | + // For server requests the Trailer map initially contains only the |
4256 | + // trailer keys, with nil values. (The client declares which trailers it |
4257 | + // will later send.) While the handler is reading from Body, it must |
4258 | + // not reference Trailer. After reading from Body returns EOF, Trailer |
4259 | + // can be read again and will contain non-nil values, if they were sent |
4260 | + // by the client. |
4261 | + // |
4262 | + // For client requests Trailer must be initialized to a map containing |
4263 | + // the trailer keys to later send. The values may be nil or their final |
4264 | + // values. The ContentLength must be 0 or -1, to send a chunked request. |
4265 | + // After the HTTP request is sent the map values can be updated while |
4266 | + // the request body is read. Once the body returns EOF, the caller must |
4267 | + // not mutate Trailer. |
4268 | + // |
4269 | + // Few HTTP clients, servers, or proxies support HTTP trailers. |
4270 | Trailer http.Header |
4271 | |
4272 | // RemoteAddr allows HTTP servers and other software to record |
4273 | @@ -382,7 +417,6 @@ |
4274 | return err |
4275 | } |
4276 | |
4277 | - // TODO: split long values? (If so, should share code with Conn.Write) |
4278 | err = req.Header.WriteSubset(w, reqWriteExcludeHeader) |
4279 | if err != nil { |
4280 | return err |
4281 | @@ -589,32 +623,6 @@ |
4282 | |
4283 | fixPragmaCacheControl(req.Header) |
4284 | |
4285 | - // TODO: Parse specific header values: |
4286 | - // Accept |
4287 | - // Accept-Encoding |
4288 | - // Accept-Language |
4289 | - // Authorization |
4290 | - // Cache-Control |
4291 | - // Connection |
4292 | - // Date |
4293 | - // Expect |
4294 | - // From |
4295 | - // If-Match |
4296 | - // If-Modified-Since |
4297 | - // If-None-Match |
4298 | - // If-Range |
4299 | - // If-Unmodified-Since |
4300 | - // Max-Forwards |
4301 | - // Proxy-Authorization |
4302 | - // Referer [sic] |
4303 | - // TE (transfer-codings) |
4304 | - // Trailer |
4305 | - // Transfer-Encoding |
4306 | - // Upgrade |
4307 | - // User-Agent |
4308 | - // Via |
4309 | - // Warning |
4310 | - |
4311 | err = readTransfer(req, b) |
4312 | if err != nil { |
4313 | return nil, err |
4314 | @@ -783,9 +791,7 @@ |
4315 | } |
4316 | |
4317 | mr, err := r.multipartReader() |
4318 | - if err == ErrNotMultipart { |
4319 | - return nil |
4320 | - } else if err != nil { |
4321 | + if err != nil { |
4322 | return err |
4323 | } |
4324 | |
4325 | @@ -863,3 +869,9 @@ |
4326 | func (r *Request) wantsClose() bool { |
4327 | return hasToken(r.Header.Get("Connection"), "close") |
4328 | } |
4329 | + |
4330 | +func (r *Request) closeBody() { |
4331 | + if r.Body != nil { |
4332 | + r.Body.Close() |
4333 | + } |
4334 | +} |
4335 | |
4336 | === modified file 'http13client/request_test.go' |
4337 | --- http13client/request_test.go 2014-03-20 09:26:28 +0000 |
4338 | +++ http13client/request_test.go 2014-06-20 13:02:58 +0000 |
4339 | @@ -155,7 +155,25 @@ |
4340 | req.Header = http.Header{"Content-Type": {"text/plain"}} |
4341 | multipart, err = req.MultipartReader() |
4342 | if multipart != nil { |
4343 | - t.Errorf("unexpected multipart for text/plain") |
4344 | + t.Error("unexpected multipart for text/plain") |
4345 | + } |
4346 | +} |
4347 | + |
4348 | +func TestParseMultipartForm(t *testing.T) { |
4349 | + req := &Request{ |
4350 | + Method: "POST", |
4351 | + Header: http.Header{"Content-Type": {`multipart/form-data; boundary="foo123"`}}, |
4352 | + Body: ioutil.NopCloser(new(bytes.Buffer)), |
4353 | + } |
4354 | + err := req.ParseMultipartForm(25) |
4355 | + if err == nil { |
4356 | + t.Error("expected multipart EOF, got nil") |
4357 | + } |
4358 | + |
4359 | + req.Header = http.Header{"Content-Type": {"text/plain"}} |
4360 | + err = req.ParseMultipartForm(25) |
4361 | + if err != ErrNotMultipart { |
4362 | + t.Error("expected ErrNotMultipart for text/plain") |
4363 | } |
4364 | } |
4365 | |
4366 | @@ -221,16 +239,38 @@ |
4367 | validateTestMultipartContents(t, req, true) |
4368 | } |
4369 | |
4370 | -func TestEmptyMultipartRequest(t *testing.T) { |
4371 | - // Test that FormValue and FormFile automatically invoke |
4372 | - // ParseMultipartForm and return the right values. |
4373 | - req, err := NewRequest("GET", "/", nil) |
4374 | - if err != nil { |
4375 | - t.Errorf("NewRequest err = %q", err) |
4376 | - } |
4377 | +func TestMissingFileMultipartRequest(t *testing.T) { |
4378 | + // Test that FormFile returns an error if |
4379 | + // the named file is missing. |
4380 | + req := newTestMultipartRequest(t) |
4381 | testMissingFile(t, req) |
4382 | } |
4383 | |
4384 | +// Test that FormValue invokes ParseMultipartForm. |
4385 | +func TestFormValueCallsParseMultipartForm(t *testing.T) { |
4386 | + req, _ := NewRequest("POST", "http://www.google.com/", strings.NewReader("z=post")) |
4387 | + req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") |
4388 | + if req.Form != nil { |
4389 | + t.Fatal("Unexpected request Form, want nil") |
4390 | + } |
4391 | + req.FormValue("z") |
4392 | + if req.Form == nil { |
4393 | + t.Fatal("ParseMultipartForm not called by FormValue") |
4394 | + } |
4395 | +} |
4396 | + |
4397 | +// Test that FormFile invokes ParseMultipartForm. |
4398 | +func TestFormFileCallsParseMultipartForm(t *testing.T) { |
4399 | + req := newTestMultipartRequest(t) |
4400 | + if req.Form != nil { |
4401 | + t.Fatal("Unexpected request Form, want nil") |
4402 | + } |
4403 | + req.FormFile("") |
4404 | + if req.Form == nil { |
4405 | + t.Fatal("ParseMultipartForm not called by FormFile") |
4406 | + } |
4407 | +} |
4408 | + |
4409 | // Test that ParseMultipartForm errors if called |
4410 | // after MultipartReader on the same request. |
4411 | func TestParseMultipartFormOrder(t *testing.T) { |
4412 | |
4413 | === modified file 'http13client/response.go' |
4414 | --- http13client/response.go 2014-03-19 23:43:25 +0000 |
4415 | +++ http13client/response.go 2014-06-20 13:02:58 +0000 |
4416 | @@ -8,6 +8,7 @@ |
4417 | |
4418 | import ( |
4419 | "bufio" |
4420 | + "bytes" |
4421 | "crypto/tls" |
4422 | "errors" |
4423 | "io" |
4424 | @@ -47,7 +48,8 @@ |
4425 | // |
4426 | // The http Client and Transport guarantee that Body is always |
4427 | // non-nil, even on responses without a body or responses with |
4428 | - // a zero-lengthed body. |
4429 | + // a zero-length body. It is the caller's responsibility to |
4430 | + // close Body. |
4431 | // |
4432 | // The Body is automatically dechunked if the server replied |
4433 | // with a "chunked" Transfer-Encoding. |
4434 | @@ -200,7 +202,6 @@ |
4435 | // |
4436 | // Body is closed after it is sent. |
4437 | func (r *Response) Write(w io.Writer) error { |
4438 | - |
4439 | // Status line |
4440 | text := r.Status |
4441 | if text == "" { |
4442 | @@ -212,10 +213,45 @@ |
4443 | protoMajor, protoMinor := strconv.Itoa(r.ProtoMajor), strconv.Itoa(r.ProtoMinor) |
4444 | statusCode := strconv.Itoa(r.StatusCode) + " " |
4445 | text = strings.TrimPrefix(text, statusCode) |
4446 | - io.WriteString(w, "HTTP/"+protoMajor+"."+protoMinor+" "+statusCode+text+"\r\n") |
4447 | + if _, err := io.WriteString(w, "HTTP/"+protoMajor+"."+protoMinor+" "+statusCode+text+"\r\n"); err != nil { |
4448 | + return err |
4449 | + } |
4450 | + |
4451 | + // Clone it, so we can modify r1 as needed. |
4452 | + r1 := new(Response) |
4453 | + *r1 = *r |
4454 | + if r1.ContentLength == 0 && r1.Body != nil { |
4455 | + // Is it actually 0 length? Or just unknown? |
4456 | + var buf [1]byte |
4457 | + n, err := r1.Body.Read(buf[:]) |
4458 | + if err != nil && err != io.EOF { |
4459 | + return err |
4460 | + } |
4461 | + if n == 0 { |
4462 | + // Reset it to a known zero reader, in case underlying one |
4463 | + // is unhappy being read repeatedly. |
4464 | + r1.Body = eofReader |
4465 | + } else { |
4466 | + r1.ContentLength = -1 |
4467 | + r1.Body = struct { |
4468 | + io.Reader |
4469 | + io.Closer |
4470 | + }{ |
4471 | + io.MultiReader(bytes.NewReader(buf[:1]), r.Body), |
4472 | + r.Body, |
4473 | + } |
4474 | + } |
4475 | + } |
4476 | + // If we're sending a non-chunked HTTP/1.1 response without a |
4477 | + // content-length, the only way to do that is the old HTTP/1.0 |
4478 | + // way, by noting the EOF with a connection close, so we need |
4479 | + // to set Close. |
4480 | + if r1.ContentLength == -1 && !r1.Close && r1.ProtoAtLeast(1, 1) && !chunked(r1.TransferEncoding) { |
4481 | + r1.Close = true |
4482 | + } |
4483 | |
4484 | // Process Body,ContentLength,Close,Trailer |
4485 | - tw, err := newTransferWriter(r) |
4486 | + tw, err := newTransferWriter(r1) |
4487 | if err != nil { |
4488 | return err |
4489 | } |
4490 | @@ -230,8 +266,19 @@ |
4491 | return err |
4492 | } |
4493 | |
4494 | + // contentLengthAlreadySent may have been already sent for |
4495 | + // POST/PUT requests, even if zero length. See Issue 8180. |
4496 | + contentLengthAlreadySent := tw.shouldSendContentLength() |
4497 | + if r1.ContentLength == 0 && !chunked(r1.TransferEncoding) && !contentLengthAlreadySent { |
4498 | + if _, err := io.WriteString(w, "Content-Length: 0\r\n"); err != nil { |
4499 | + return err |
4500 | + } |
4501 | + } |
4502 | + |
4503 | // End-of-header |
4504 | - io.WriteString(w, "\r\n") |
4505 | + if _, err := io.WriteString(w, "\r\n"); err != nil { |
4506 | + return err |
4507 | + } |
4508 | |
4509 | // Write body and trailer |
4510 | err = tw.WriteBody(w) |
4511 | |
4512 | === modified file 'http13client/response_test.go' |
4513 | --- http13client/response_test.go 2014-03-19 23:13:58 +0000 |
4514 | +++ http13client/response_test.go 2014-06-20 13:02:58 +0000 |
4515 | @@ -30,6 +30,10 @@ |
4516 | return &Request{Method: method} |
4517 | } |
4518 | |
4519 | +func dummyReq11(method string) *Request { |
4520 | + return &Request{Method: method, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1} |
4521 | +} |
4522 | + |
4523 | var respTests = []respTest{ |
4524 | // Unchunked response without Content-Length. |
4525 | { |
4526 | |
4527 | === modified file 'http13client/responsewrite_test.go' |
4528 | --- http13client/responsewrite_test.go 2014-03-19 23:13:58 +0000 |
4529 | +++ http13client/responsewrite_test.go 2014-06-20 13:02:58 +0000 |
4530 | @@ -27,7 +27,7 @@ |
4531 | ProtoMinor: 0, |
4532 | Request: dummyReq("GET"), |
4533 | Header: http.Header{}, |
4534 | - Body: ioutil.NopCloser(bytes.NewBufferString("abcdef")), |
4535 | + Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
4536 | ContentLength: 6, |
4537 | }, |
4538 | |
4539 | @@ -50,6 +50,106 @@ |
4540 | "\r\n" + |
4541 | "abcdef", |
4542 | }, |
4543 | + // HTTP/1.1 response with unknown length and Connection: close |
4544 | + { |
4545 | + Response{ |
4546 | + StatusCode: 200, |
4547 | + ProtoMajor: 1, |
4548 | + ProtoMinor: 1, |
4549 | + Request: dummyReq("GET"), |
4550 | + Header: http.Header{}, |
4551 | + Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
4552 | + ContentLength: -1, |
4553 | + Close: true, |
4554 | + }, |
4555 | + "HTTP/1.1 200 OK\r\n" + |
4556 | + "Connection: close\r\n" + |
4557 | + "\r\n" + |
4558 | + "abcdef", |
4559 | + }, |
4560 | + // HTTP/1.1 response with unknown length and not setting connection: close |
4561 | + { |
4562 | + Response{ |
4563 | + StatusCode: 200, |
4564 | + ProtoMajor: 1, |
4565 | + ProtoMinor: 1, |
4566 | + Request: dummyReq11("GET"), |
4567 | + Header: http.Header{}, |
4568 | + Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
4569 | + ContentLength: -1, |
4570 | + Close: false, |
4571 | + }, |
4572 | + "HTTP/1.1 200 OK\r\n" + |
4573 | + "Connection: close\r\n" + |
4574 | + "\r\n" + |
4575 | + "abcdef", |
4576 | + }, |
4577 | + // HTTP/1.1 response with unknown length and not setting connection: close, but |
4578 | + // setting chunked. |
4579 | + { |
4580 | + Response{ |
4581 | + StatusCode: 200, |
4582 | + ProtoMajor: 1, |
4583 | + ProtoMinor: 1, |
4584 | + Request: dummyReq11("GET"), |
4585 | + Header: http.Header{}, |
4586 | + Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
4587 | + ContentLength: -1, |
4588 | + TransferEncoding: []string{"chunked"}, |
4589 | + Close: false, |
4590 | + }, |
4591 | + "HTTP/1.1 200 OK\r\n" + |
4592 | + "Transfer-Encoding: chunked\r\n\r\n" + |
4593 | + "6\r\nabcdef\r\n0\r\n\r\n", |
4594 | + }, |
4595 | + // HTTP/1.1 response 0 content-length, and nil body |
4596 | + { |
4597 | + Response{ |
4598 | + StatusCode: 200, |
4599 | + ProtoMajor: 1, |
4600 | + ProtoMinor: 1, |
4601 | + Request: dummyReq11("GET"), |
4602 | + Header: http.Header{}, |
4603 | + Body: nil, |
4604 | + ContentLength: 0, |
4605 | + Close: false, |
4606 | + }, |
4607 | + "HTTP/1.1 200 OK\r\n" + |
4608 | + "Content-Length: 0\r\n" + |
4609 | + "\r\n", |
4610 | + }, |
4611 | + // HTTP/1.1 response 0 content-length, and non-nil empty body |
4612 | + { |
4613 | + Response{ |
4614 | + StatusCode: 200, |
4615 | + ProtoMajor: 1, |
4616 | + ProtoMinor: 1, |
4617 | + Request: dummyReq11("GET"), |
4618 | + Header: http.Header{}, |
4619 | + Body: ioutil.NopCloser(strings.NewReader("")), |
4620 | + ContentLength: 0, |
4621 | + Close: false, |
4622 | + }, |
4623 | + "HTTP/1.1 200 OK\r\n" + |
4624 | + "Content-Length: 0\r\n" + |
4625 | + "\r\n", |
4626 | + }, |
4627 | + // HTTP/1.1 response 0 content-length, and non-nil non-empty body |
4628 | + { |
4629 | + Response{ |
4630 | + StatusCode: 200, |
4631 | + ProtoMajor: 1, |
4632 | + ProtoMinor: 1, |
4633 | + Request: dummyReq11("GET"), |
4634 | + Header: http.Header{}, |
4635 | + Body: ioutil.NopCloser(strings.NewReader("foo")), |
4636 | + ContentLength: 0, |
4637 | + Close: false, |
4638 | + }, |
4639 | + "HTTP/1.1 200 OK\r\n" + |
4640 | + "Connection: close\r\n" + |
4641 | + "\r\nfoo", |
4642 | + }, |
4643 | // HTTP/1.1, chunked coding; empty trailer; close |
4644 | { |
4645 | Response{ |
4646 | @@ -92,6 +192,22 @@ |
4647 | "Foo: Bar Baz\r\n" + |
4648 | "\r\n", |
4649 | }, |
4650 | + |
4651 | + // Want a single Content-Length header. Fixing issue 8180 where |
4652 | + // there were two. |
4653 | + { |
4654 | + Response{ |
4655 | + StatusCode: http.StatusOK, |
4656 | + ProtoMajor: 1, |
4657 | + ProtoMinor: 1, |
4658 | + Request: &Request{Method: "POST"}, |
4659 | + Header: http.Header{}, |
4660 | + ContentLength: 0, |
4661 | + TransferEncoding: nil, |
4662 | + Body: nil, |
4663 | + }, |
4664 | + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", |
4665 | + }, |
4666 | } |
4667 | |
4668 | for i := range respWriteTests { |
4669 | |
4670 | === modified file 'http13client/server.go' |
4671 | --- http13client/server.go 2014-03-19 23:13:58 +0000 |
4672 | +++ http13client/server.go 2014-06-20 13:02:58 +0000 |
4673 | @@ -10,21 +10,27 @@ |
4674 | "io/ioutil" |
4675 | "log" |
4676 | "net" |
4677 | - "strings" |
4678 | "sync" |
4679 | ) |
4680 | |
4681 | +type eofReaderWithWriteTo struct{} |
4682 | + |
4683 | +func (eofReaderWithWriteTo) WriteTo(io.Writer) (int64, error) { return 0, nil } |
4684 | +func (eofReaderWithWriteTo) Read([]byte) (int, error) { return 0, io.EOF } |
4685 | + |
4686 | // eofReader is a non-nil io.ReadCloser that always returns EOF. |
4687 | -// It embeds a *strings.Reader so it still has a WriteTo method |
4688 | -// and io.Copy won't need a buffer. |
4689 | +// It has a WriteTo method so io.Copy won't need a buffer. |
4690 | var eofReader = &struct { |
4691 | - *strings.Reader |
4692 | + eofReaderWithWriteTo |
4693 | io.Closer |
4694 | }{ |
4695 | - strings.NewReader(""), |
4696 | + eofReaderWithWriteTo{}, |
4697 | ioutil.NopCloser(nil), |
4698 | } |
4699 | |
4700 | +// Verify that an io.Copy from an eofReader won't require a buffer. |
4701 | +var _ io.WriterTo = eofReader |
4702 | + |
4703 | // loggingConn is used for debugging. |
4704 | type loggingConn struct { |
4705 | name string |
4706 | |
4707 | === modified file 'http13client/transfer.go' |
4708 | --- http13client/transfer.go 2014-03-19 23:13:58 +0000 |
4709 | +++ http13client/transfer.go 2014-06-20 13:02:58 +0000 |
4710 | @@ -13,6 +13,7 @@ |
4711 | "io/ioutil" |
4712 | "net/http" |
4713 | "net/textproto" |
4714 | + "sort" |
4715 | "strconv" |
4716 | "strings" |
4717 | "sync" |
4718 | @@ -144,11 +145,10 @@ |
4719 | return false |
4720 | } |
4721 | |
4722 | -func (t *transferWriter) WriteHeader(w io.Writer) (err error) { |
4723 | +func (t *transferWriter) WriteHeader(w io.Writer) error { |
4724 | if t.Close { |
4725 | - _, err = io.WriteString(w, "Connection: close\r\n") |
4726 | - if err != nil { |
4727 | - return |
4728 | + if _, err := io.WriteString(w, "Connection: close\r\n"); err != nil { |
4729 | + return err |
4730 | } |
4731 | } |
4732 | |
4733 | @@ -156,43 +156,44 @@ |
4734 | // function of the sanitized field triple (Body, ContentLength, |
4735 | // TransferEncoding) |
4736 | if t.shouldSendContentLength() { |
4737 | - io.WriteString(w, "Content-Length: ") |
4738 | - _, err = io.WriteString(w, strconv.FormatInt(t.ContentLength, 10)+"\r\n") |
4739 | - if err != nil { |
4740 | - return |
4741 | + if _, err := io.WriteString(w, "Content-Length: "); err != nil { |
4742 | + return err |
4743 | + } |
4744 | + if _, err := io.WriteString(w, strconv.FormatInt(t.ContentLength, 10)+"\r\n"); err != nil { |
4745 | + return err |
4746 | } |
4747 | } else if chunked(t.TransferEncoding) { |
4748 | - _, err = io.WriteString(w, "Transfer-Encoding: chunked\r\n") |
4749 | - if err != nil { |
4750 | - return |
4751 | + if _, err := io.WriteString(w, "Transfer-Encoding: chunked\r\n"); err != nil { |
4752 | + return err |
4753 | } |
4754 | } |
4755 | |
4756 | // Write Trailer header |
4757 | if t.Trailer != nil { |
4758 | - // TODO: At some point, there should be a generic mechanism for |
4759 | - // writing long headers, using HTTP line splitting |
4760 | - io.WriteString(w, "Trailer: ") |
4761 | - needComma := false |
4762 | + keys := make([]string, 0, len(t.Trailer)) |
4763 | for k := range t.Trailer { |
4764 | k = http.CanonicalHeaderKey(k) |
4765 | switch k { |
4766 | case "Transfer-Encoding", "Trailer", "Content-Length": |
4767 | return &badStringError{"invalid Trailer key", k} |
4768 | } |
4769 | - if needComma { |
4770 | - io.WriteString(w, ",") |
4771 | + keys = append(keys, k) |
4772 | + } |
4773 | + if len(keys) > 0 { |
4774 | + sort.Strings(keys) |
4775 | + // TODO: could do better allocation-wise here, but trailers are rare, |
4776 | + // so being lazy for now. |
4777 | + if _, err := io.WriteString(w, "Trailer: "+strings.Join(keys, ",")+"\r\n"); err != nil { |
4778 | + return err |
4779 | } |
4780 | - io.WriteString(w, k) |
4781 | - needComma = true |
4782 | } |
4783 | - _, err = io.WriteString(w, "\r\n") |
4784 | } |
4785 | |
4786 | - return |
4787 | + return nil |
4788 | } |
4789 | |
4790 | -func (t *transferWriter) WriteBody(w io.Writer) (err error) { |
4791 | +func (t *transferWriter) WriteBody(w io.Writer) error { |
4792 | + var err error |
4793 | var ncopy int64 |
4794 | |
4795 | // Write body |
4796 | @@ -229,11 +230,16 @@ |
4797 | |
4798 | // TODO(petar): Place trailer writer code here. |
4799 | if chunked(t.TransferEncoding) { |
4800 | + // Write Trailer header |
4801 | + if t.Trailer != nil { |
4802 | + if err := t.Trailer.Write(w); err != nil { |
4803 | + return err |
4804 | + } |
4805 | + } |
4806 | // Last chunk, empty trailer |
4807 | _, err = io.WriteString(w, "\r\n") |
4808 | } |
4809 | - |
4810 | - return |
4811 | + return err |
4812 | } |
4813 | |
4814 | type transferReader struct { |
4815 | @@ -265,6 +271,22 @@ |
4816 | return true |
4817 | } |
4818 | |
4819 | +var ( |
4820 | + suppressedHeaders304 = []string{"Content-Type", "Content-Length", "Transfer-Encoding"} |
4821 | + suppressedHeadersNoBody = []string{"Content-Length", "Transfer-Encoding"} |
4822 | +) |
4823 | + |
4824 | +func suppressedHeaders(status int) []string { |
4825 | + switch { |
4826 | + case status == 304: |
4827 | + // RFC 2616 section 10.3.5: "the response MUST NOT include other entity-headers" |
4828 | + return suppressedHeaders304 |
4829 | + case !bodyAllowedForStatus(status): |
4830 | + return suppressedHeadersNoBody |
4831 | + } |
4832 | + return nil |
4833 | +} |
4834 | + |
4835 | // msg is *Request or *Response. |
4836 | func readTransfer(msg interface{}, r *bufio.Reader) (err error) { |
4837 | t := &transferReader{RequestMethod: "GET"} |
4838 | @@ -511,7 +533,7 @@ |
4839 | case "Transfer-Encoding", "Trailer", "Content-Length": |
4840 | return nil, &badStringError{"bad trailer key", key} |
4841 | } |
4842 | - trailer.Del(key) |
4843 | + trailer[key] = nil |
4844 | } |
4845 | if len(trailer) == 0 { |
4846 | return nil, nil |
4847 | @@ -643,13 +665,23 @@ |
4848 | } |
4849 | switch rr := b.hdr.(type) { |
4850 | case *Request: |
4851 | - rr.Trailer = http.Header(hdr) |
4852 | + mergeSetHeader(&rr.Trailer, http.Header(hdr)) |
4853 | case *Response: |
4854 | - rr.Trailer = http.Header(hdr) |
4855 | + mergeSetHeader(&rr.Trailer, http.Header(hdr)) |
4856 | } |
4857 | return nil |
4858 | } |
4859 | |
4860 | +func mergeSetHeader(dst *http.Header, src http.Header) { |
4861 | + if *dst == nil { |
4862 | + *dst = src |
4863 | + return |
4864 | + } |
4865 | + for k, vv := range src { |
4866 | + (*dst)[k] = vv |
4867 | + } |
4868 | +} |
4869 | + |
4870 | func (b *body) Close() error { |
4871 | b.mu.Lock() |
4872 | defer b.mu.Unlock() |
4873 | |
4874 | === modified file 'http13client/transport.go' |
4875 | --- http13client/transport.go 2014-03-20 09:26:28 +0000 |
4876 | +++ http13client/transport.go 2014-06-20 13:02:58 +0000 |
4877 | @@ -110,6 +110,9 @@ |
4878 | // An error is returned if the proxy environment is invalid. |
4879 | // A nil URL and nil error are returned if no proxy is defined in the |
4880 | // environment, or a proxy should not be used for the given request. |
4881 | +// |
4882 | +// As a special case, if req.URL.Host is "localhost" (with or without |
4883 | +// a port number), then a nil URL and nil error will be returned. |
4884 | func ProxyFromEnvironment(req *Request) (*url.URL, error) { |
4885 | proxy := httpProxyEnv.Get() |
4886 | if proxy == "" { |
4887 | @@ -161,9 +164,11 @@ |
4888 | // and redirects), see Get, Post, and the Client type. |
4889 | func (t *Transport) RoundTrip(req *Request) (resp *Response, err error) { |
4890 | if req.URL == nil { |
4891 | + req.closeBody() |
4892 | return nil, errors.New("http: nil Request.URL") |
4893 | } |
4894 | if req.Header == nil { |
4895 | + req.closeBody() |
4896 | return nil, errors.New("http: nil Request.Header") |
4897 | } |
4898 | if req.URL.Scheme != "http" && req.URL.Scheme != "https" { |
4899 | @@ -174,16 +179,19 @@ |
4900 | } |
4901 | t.altMu.RUnlock() |
4902 | if rt == nil { |
4903 | + req.closeBody() |
4904 | return nil, &badStringError{"unsupported protocol scheme", req.URL.Scheme} |
4905 | } |
4906 | return rt.RoundTrip(req) |
4907 | } |
4908 | if req.URL.Host == "" { |
4909 | + req.closeBody() |
4910 | return nil, errors.New("http: no Host in request URL") |
4911 | } |
4912 | treq := &transportRequest{Request: req} |
4913 | cm, err := t.connectMethodForRequest(treq) |
4914 | if err != nil { |
4915 | + req.closeBody() |
4916 | return nil, err |
4917 | } |
4918 | |
4919 | @@ -194,6 +202,7 @@ |
4920 | pconn, err := t.getConn(req, cm) |
4921 | if err != nil { |
4922 | t.setReqCanceler(req, nil) |
4923 | + req.closeBody() |
4924 | return nil, err |
4925 | } |
4926 | |
4927 | @@ -231,9 +240,6 @@ |
4928 | t.idleConn = nil |
4929 | t.idleConnCh = nil |
4930 | t.idleMu.Unlock() |
4931 | - if m == nil { |
4932 | - return |
4933 | - } |
4934 | for _, conns := range m { |
4935 | for _, pconn := range conns { |
4936 | pconn.close() |
4937 | @@ -499,12 +505,13 @@ |
4938 | pa := cm.proxyAuth() |
4939 | |
4940 | pconn := &persistConn{ |
4941 | - t: t, |
4942 | - cacheKey: cm.key(), |
4943 | - conn: conn, |
4944 | - reqch: make(chan requestAndChan, 50), |
4945 | - writech: make(chan writeRequest, 50), |
4946 | - closech: make(chan struct{}), |
4947 | + t: t, |
4948 | + cacheKey: cm.key(), |
4949 | + conn: conn, |
4950 | + reqch: make(chan requestAndChan, 1), |
4951 | + writech: make(chan writeRequest, 1), |
4952 | + closech: make(chan struct{}), |
4953 | + writeErrCh: make(chan error, 1), |
4954 | } |
4955 | |
4956 | switch { |
4957 | @@ -589,7 +596,7 @@ |
4958 | pconn.conn = tlsConn |
4959 | } |
4960 | |
4961 | - pconn.br = bufio.NewReader(pconn.conn) |
4962 | + pconn.br = bufio.NewReader(noteEOFReader{pconn.conn, &pconn.sawEOF}) |
4963 | pconn.bw = bufio.NewWriter(pconn.conn) |
4964 | go pconn.readLoop() |
4965 | go pconn.writeLoop() |
4966 | @@ -722,16 +729,22 @@ |
4967 | cacheKey connectMethodKey |
4968 | conn net.Conn |
4969 | tlsState *tls.ConnectionState |
4970 | - closed bool // whether conn has been closed |
4971 | br *bufio.Reader // from conn |
4972 | + sawEOF bool // whether we've seen EOF from conn; owned by readLoop |
4973 | bw *bufio.Writer // to conn |
4974 | reqch chan requestAndChan // written by roundTrip; read by readLoop |
4975 | writech chan writeRequest // written by roundTrip; read by writeLoop |
4976 | - closech chan struct{} // broadcast close when readLoop (TCP connection) closes |
4977 | + closech chan struct{} // closed when conn closed |
4978 | isProxy bool |
4979 | + // writeErrCh passes the request write error (usually nil) |
4980 | + // from the writeLoop goroutine to the readLoop which passes |
4981 | + // it off to the res.Body reader, which then uses it to decide |
4982 | + // whether or not a connection can be reused. Issue 7569. |
4983 | + writeErrCh chan error |
4984 | |
4985 | - lk sync.Mutex // guards following 3 fields |
4986 | + lk sync.Mutex // guards following fields |
4987 | numExpectedResponses int |
4988 | + closed bool // whether conn has been closed |
4989 | broken bool // an error has happened on this connection; marked broken so it's not reused. |
4990 | // mutateHeaderFunc is an optional func to modify extra |
4991 | // headers on each outbound request before it's written. (the |
4992 | @@ -739,6 +752,7 @@ |
4993 | mutateHeaderFunc func(http.Header) |
4994 | } |
4995 | |
4996 | +// isBroken reports whether this connection is in a known broken state. |
4997 | func (pc *persistConn) isBroken() bool { |
4998 | pc.lk.Lock() |
4999 | b := pc.broken |
5000 | @@ -763,7 +777,6 @@ |
The diff has been truncated for viewing.