Merge lp:~pedronis/ubuntu-push/update-http13 into lp:ubuntu-push

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
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
=== modified file 'bus/endpoint.go'
--- bus/endpoint.go 2014-05-15 11:28:06 +0000
+++ bus/endpoint.go 2014-06-20 13:02:58 +0000
@@ -29,7 +29,7 @@
29 * Endpoint (and its implementation)29 * Endpoint (and its implementation)
30 */30 */
3131
32type BusMethod func([]interface{}, []interface{}) ([]interface{}, error)32type BusMethod func(string, []interface{}, []interface{}) ([]interface{}, error)
33type DispatchMap map[string]BusMethod33type DispatchMap map[string]BusMethod
3434
35// bus.Endpoint represents the DBus connection itself.35// bus.Endpoint represents the DBus connection itself.
@@ -249,12 +249,12 @@
249 endp.log.Errorf("WatchMethod: unknown method %s", msg.Member)249 endp.log.Errorf("WatchMethod: unknown method %s", msg.Member)
250 } else {250 } else {
251 args := msg.AllArgs()251 args := msg.AllArgs()
252 rvals, err := meth(args, extra)252 rvals, err := meth(string(msg.Path), args, extra)
253 if err != nil {253 if err != nil {
254 reply = dbus.NewErrorMessage(msg, err_iface, err.Error())254 reply = dbus.NewErrorMessage(msg, err_iface, err.Error())
255 endp.log.Errorf("WatchMethod: %s(%#v, %#v) failure: %#v", msg.Member, args, extra, err)255 endp.log.Errorf("WatchMethod: %s(%v, %#v, %#v) failure: %#v", msg.Member, msg.Path, args, extra, err)
256 } else {256 } else {
257 endp.log.Debugf("WatchMethod: %s(%#v, %#v) success: %#v", msg.Member, args, extra, rvals)257 endp.log.Debugf("WatchMethod: %s(%v, %#v, %#v) success: %#v", msg.Member, msg.Path, args, extra, rvals)
258 reply = dbus.NewMethodReturnMessage(msg)258 reply = dbus.NewMethodReturnMessage(msg)
259 err = reply.AppendArgs(rvals...)259 err = reply.AppendArgs(rvals...)
260 if err != nil {260 if err != nil {
261261
=== modified file 'bus/testing/testing_endpoint_test.go'
--- bus/testing/testing_endpoint_test.go 2014-06-02 10:04:34 +0000
+++ bus/testing/testing_endpoint_test.go 2014-06-20 13:02:58 +0000
@@ -229,7 +229,7 @@
229// Test that WatchMethod updates callArgs229// Test that WatchMethod updates callArgs
230func (s *TestingEndpointSuite) TestWatchMethodUpdatesCallArgs(c *C) {230func (s *TestingEndpointSuite) TestWatchMethodUpdatesCallArgs(c *C) {
231 endp := NewTestingEndpoint(nil, condition.Work(true))231 endp := NewTestingEndpoint(nil, condition.Work(true))
232 foo := func([]interface{}, []interface{}) ([]interface{}, error) { return nil, nil }232 foo := func(string, []interface{}, []interface{}) ([]interface{}, error) { return nil, nil }
233 foomp := bus.DispatchMap{"foo": foo}233 foomp := bus.DispatchMap{"foo": foo}
234 endp.WatchMethod(foomp)234 endp.WatchMethod(foomp)
235 c.Check(GetCallArgs(endp), DeepEquals, []callArgs{235 c.Check(GetCallArgs(endp), DeepEquals, []callArgs{
236236
=== modified file 'client/client.go'
--- client/client.go 2014-05-21 10:03:18 +0000
+++ client/client.go 2014-06-20 13:02:58 +0000
@@ -28,6 +28,7 @@
28 "fmt"28 "fmt"
29 "io/ioutil"29 "io/ioutil"
30 "os"30 "os"
31 "os/exec"
31 "strings"32 "strings"
3233
33 "launchpad.net/go-dbus/v1"34 "launchpad.net/go-dbus/v1"
@@ -63,32 +64,36 @@
63 // The PEM-encoded server certificate64 // The PEM-encoded server certificate
64 CertPEMFile string `json:"cert_pem_file"`65 CertPEMFile string `json:"cert_pem_file"`
65 // How to invoke the auth helper66 // How to invoke the auth helper
66 AuthHelper []string `json:"auth_helper"`67 AuthHelper string `json:"auth_helper"`
68 SessionURL string `json:"session_url"`
69 RegistrationURL string `json:"registration_url"`
67 // The logging level (one of "debug", "info", "error")70 // The logging level (one of "debug", "info", "error")
68 LogLevel logger.ConfigLogLevel `json:"log_level"`71 LogLevel logger.ConfigLogLevel `json:"log_level"`
69}72}
7073
71// PushClient is the Ubuntu Push Notifications client-side daemon.74// PushClient is the Ubuntu Push Notifications client-side daemon.
72type PushClient struct {75type PushClient struct {
73 leveldbPath string76 leveldbPath string
74 configPath string77 configPath string
75 config ClientConfig78 config ClientConfig
76 log logger.Logger79 log logger.Logger
77 pem []byte80 pem []byte
78 idder identifier.Id81 idder identifier.Id
79 deviceId string82 deviceId string
80 notificationsEndp bus.Endpoint83 notificationsEndp bus.Endpoint
81 urlDispatcherEndp bus.Endpoint84 urlDispatcherEndp bus.Endpoint
82 connectivityEndp bus.Endpoint85 connectivityEndp bus.Endpoint
83 systemImageEndp bus.Endpoint86 systemImageEndp bus.Endpoint
84 systemImageInfo *systemimage.InfoResult87 systemImageInfo *systemimage.InfoResult
85 connCh chan bool88 connCh chan bool
86 hasConnectivity bool89 hasConnectivity bool
87 actionsCh <-chan notifications.RawActionReply90 actionsCh <-chan notifications.RawActionReply
88 session *session.ClientSession91 session *session.ClientSession
89 sessionConnectedCh chan uint3292 sessionConnectedCh chan uint32
90 serviceEndpoint bus.Endpoint93 pushServiceEndpoint bus.Endpoint
91 service *service.Service94 pushService *service.PushService
95 postalServiceEndpoint bus.Endpoint
96 postalService *service.PostalService
92}97}
9398
94var (99var (
@@ -160,7 +165,27 @@
160 ExpectAllRepairedTime: client.config.ExpectAllRepairedTime.TimeDuration(),165 ExpectAllRepairedTime: client.config.ExpectAllRepairedTime.TimeDuration(),
161 PEM: client.pem,166 PEM: client.pem,
162 Info: info,167 Info: info,
163 AuthHelper: client.config.AuthHelper,168 AuthGetter: client.getAuthorization,
169 AuthURL: client.config.SessionURL,
170 }
171}
172
173// getAuthorization gets the authorization blob to send to the server
174func (client *PushClient) getAuthorization(url string) string {
175 client.log.Debugf("getting authorization for %s", url)
176 // using a helper, for now at least
177 if len(client.config.AuthHelper) == 0 {
178 // do nothing if helper is unset or empty
179 return ""
180 }
181
182 auth, err := exec.Command(client.config.AuthHelper, url).Output()
183 if err != nil {
184 // For now we just log the error, as we don't want to block unauthorized users
185 client.log.Errorf("unable to get the authorization token from the account: %v", err)
186 return ""
187 } else {
188 return strings.TrimSpace(string(auth))
164 }189 }
165}190}
166191
@@ -331,7 +356,7 @@
331// handleUnicastNotification deals with receiving a unicast notification356// handleUnicastNotification deals with receiving a unicast notification
332func (client *PushClient) handleUnicastNotification(msg *protocol.Notification) error {357func (client *PushClient) handleUnicastNotification(msg *protocol.Notification) error {
333 client.log.Debugf("sending notification %#v for %#v.", msg.MsgId, msg.AppId)358 client.log.Debugf("sending notification %#v for %#v.", msg.MsgId, msg.AppId)
334 return client.service.Inject(msg.AppId, string(msg.Payload))359 return client.postalService.Inject(msg.AppId, string(msg.Payload))
335}360}
336361
337// handleClick deals with the user clicking a notification362// handleClick deals with the user clicking a notification
@@ -422,13 +447,25 @@
422}447}
423448
424func (client *PushClient) startService() error {449func (client *PushClient) startService() error {
425 if client.serviceEndpoint == nil {450 if client.pushServiceEndpoint == nil {
426 client.serviceEndpoint = bus.SessionBus.Endpoint(service.BusAddress, client.log)451 client.pushServiceEndpoint = bus.SessionBus.Endpoint(service.PushServiceBusAddress, client.log)
452 }
453 if client.postalServiceEndpoint == nil {
454 client.postalServiceEndpoint = bus.SessionBus.Endpoint(service.PostalServiceBusAddress, client.log)
427 }455 }
428456
429 client.service = service.NewService(client.serviceEndpoint, client.log)457 client.pushService = service.NewPushService(client.pushServiceEndpoint, client.log)
430 client.service.SetMessageHandler(client.messageHandler)458 client.pushService.SetRegistrationURL(client.config.RegistrationURL)
431 return client.service.Start()459 client.pushService.SetAuthGetter(client.getAuthorization)
460 client.postalService = service.NewPostalService(client.postalServiceEndpoint, client.log)
461 client.postalService.SetMessageHandler(client.messageHandler)
462 if err := client.pushService.Start(); err != nil {
463 return err
464 }
465 if err := client.postalService.Start(); err != nil {
466 return err
467 }
468 return nil
432}469}
433470
434// Start calls doStart with the "real" starters471// Start calls doStart with the "real" starters
435472
=== modified file 'client/client_test.go'
--- client/client_test.go 2014-05-20 13:56:37 +0000
+++ client/client_test.go 2014-06-20 13:02:58 +0000
@@ -103,11 +103,13 @@
103 "stabilizing_timeout": "0ms",103 "stabilizing_timeout": "0ms",
104 "connectivity_check_url": "",104 "connectivity_check_url": "",
105 "connectivity_check_md5": "",105 "connectivity_check_md5": "",
106 "addr": ":0",106 "addr": ":0",
107 "cert_pem_file": pem_file,107 "cert_pem_file": pem_file,
108 "recheck_timeout": "3h",108 "recheck_timeout": "3h",
109 "auth_helper": []string{},109 "auth_helper": "",
110 "log_level": "debug",110 "session_url": "xyzzy://",
111 "registration_url": "reg://",
112 "log_level": "debug",
111 }113 }
112 for k, v := range overrides {114 for k, v := range overrides {
113 cfgMap[k] = v115 cfgMap[k] = v
@@ -257,7 +259,7 @@
257259
258func (cs *clientSuite) TestDeriveSessionConfig(c *C) {260func (cs *clientSuite) TestDeriveSessionConfig(c *C) {
259 cs.writeTestConfig(map[string]interface{}{261 cs.writeTestConfig(map[string]interface{}{
260 "auth_helper": []string{"auth", "helper"},262 "auth_helper": "auth helper",
261 })263 })
262 info := map[string]interface{}{264 info := map[string]interface{}{
263 "foo": 1,265 "foo": 1,
@@ -272,7 +274,8 @@
272 ExpectAllRepairedTime: 30 * time.Minute,274 ExpectAllRepairedTime: 30 * time.Minute,
273 PEM: cli.pem,275 PEM: cli.pem,
274 Info: info,276 Info: info,
275 AuthHelper: []string{"auth", "helper"},277 AuthGetter: func(string) string { return "" },
278 AuthURL: "xyzzy://",
276 }279 }
277 // sanity check that we are looking at all fields280 // sanity check that we are looking at all fields
278 vExpected := reflect.ValueOf(expected)281 vExpected := reflect.ValueOf(expected)
@@ -284,6 +287,11 @@
284 }287 }
285 // finally compare288 // finally compare
286 conf := cli.deriveSessionConfig(info)289 conf := cli.deriveSessionConfig(info)
290 // compare authGetter by string
291 c.Check(fmt.Sprintf("%v", conf.AuthGetter), Equals, fmt.Sprintf("%v", cli.getAuthorization))
292 // and set it to nil
293 conf.AuthGetter = nil
294 expected.AuthGetter = nil
287 c.Check(conf, DeepEquals, expected)295 c.Check(conf, DeepEquals, expected)
288}296}
289297
@@ -292,15 +300,23 @@
292******************************************************************/300******************************************************************/
293301
294func (cs *clientSuite) TestStartServiceWorks(c *C) {302func (cs *clientSuite) TestStartServiceWorks(c *C) {
303 cs.writeTestConfig(map[string]interface{}{
304 "auth_helper": "../scripts/dummyauth.sh",
305 })
295 cli := NewPushClient(cs.configPath, cs.leveldbPath)306 cli := NewPushClient(cs.configPath, cs.leveldbPath)
307 cli.configure()
296 cli.log = cs.log308 cli.log = cs.log
297 cli.serviceEndpoint = testibus.NewTestingEndpoint(condition.Work(true), nil)309 cli.pushServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(true), nil)
298 c.Check(cli.service, IsNil)310 cli.postalServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(true), nil)
311 c.Check(cli.pushService, IsNil)
299 c.Check(cli.startService(), IsNil)312 c.Check(cli.startService(), IsNil)
300 c.Assert(cli.service, NotNil)313 c.Assert(cli.pushService, NotNil)
301 c.Check(cli.service.IsRunning(), Equals, true)314 c.Check(cli.pushService.IsRunning(), Equals, true)
302 c.Check(cli.service.GetMessageHandler(), NotNil)315 c.Check(cli.postalService.IsRunning(), Equals, true)
303 cli.service.Stop()316 c.Check(cli.postalService.GetMessageHandler(), NotNil)
317 c.Check(cli.pushService.GetRegistrationAuthorization(), Equals, "hello reg://")
318 cli.pushService.Stop()
319 cli.postalService.Stop()
304}320}
305321
306func (cs *clientSuite) TestStartServiceErrorsOnNilLog(c *C) {322func (cs *clientSuite) TestStartServiceErrorsOnNilLog(c *C) {
@@ -312,7 +328,8 @@
312func (cs *clientSuite) TestStartServiceErrorsOnBusDialFail(c *C) {328func (cs *clientSuite) TestStartServiceErrorsOnBusDialFail(c *C) {
313 cli := NewPushClient(cs.configPath, cs.leveldbPath)329 cli := NewPushClient(cs.configPath, cs.leveldbPath)
314 cli.log = cs.log330 cli.log = cs.log
315 cli.serviceEndpoint = testibus.NewTestingEndpoint(condition.Work(false), nil)331 cli.pushServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(false), nil)
332 cli.postalServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(false), nil)
316 c.Check(cli.startService(), NotNil)333 c.Check(cli.startService(), NotNil)
317}334}
318335
@@ -647,14 +664,16 @@
647func (cs *clientSuite) TestHandleUcastNotification(c *C) {664func (cs *clientSuite) TestHandleUcastNotification(c *C) {
648 cli := NewPushClient(cs.configPath, cs.leveldbPath)665 cli := NewPushClient(cs.configPath, cs.leveldbPath)
649 svcEndp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1))666 svcEndp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1))
667 postEndp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1))
650 cli.log = cs.log668 cli.log = cs.log
651 cli.serviceEndpoint = svcEndp669 cli.pushServiceEndpoint = svcEndp
670 cli.postalServiceEndpoint = postEndp
652 notsEndp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))671 notsEndp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
653 cli.notificationsEndp = notsEndp672 cli.notificationsEndp = notsEndp
654 c.Assert(cli.startService(), IsNil)673 c.Assert(cli.startService(), IsNil)
655 c.Check(cli.handleUnicastNotification(notif), IsNil)674 c.Check(cli.handleUnicastNotification(notif), IsNil)
656 // check we sent the notification675 // check we sent the notification
657 args := testibus.GetCallArgs(svcEndp)676 args := testibus.GetCallArgs(postEndp)
658 c.Assert(len(args), Not(Equals), 0)677 c.Assert(len(args), Not(Equals), 0)
659 c.Check(args[len(args)-1].Member, Equals, "::Signal")678 c.Check(args[len(args)-1].Member, Equals, "::Signal")
660 c.Check(cs.log.Captured(), Matches, `(?m).*sending notification "42" for "hello".*`)679 c.Check(cs.log.Captured(), Matches, `(?m).*sending notification "42" for "hello".*`)
@@ -879,7 +898,7 @@
879 cli := NewPushClient(cs.configPath, cs.leveldbPath)898 cli := NewPushClient(cs.configPath, cs.leveldbPath)
880 // before start, everything sucks:899 // before start, everything sucks:
881 // no service,900 // no service,
882 c.Check(cli.service, IsNil)901 c.Check(cli.pushService, IsNil)
883 // no config,902 // no config,
884 c.Check(string(cli.config.Addr), Equals, "")903 c.Check(string(cli.config.Addr), Equals, "")
885 // no device id,904 // no device id,
@@ -904,9 +923,10 @@
904 // and a bus,923 // and a bus,
905 c.Check(cli.notificationsEndp, NotNil)924 c.Check(cli.notificationsEndp, NotNil)
906 // and a service,925 // and a service,
907 c.Check(cli.service, NotNil)926 c.Check(cli.pushService, NotNil)
908 // and everthying us just peachy!927 // and everthying us just peachy!
909 cli.service.Stop() // cleanup928 cli.pushService.Stop() // cleanup
929 cli.postalService.Stop() // cleanup
910}930}
911931
912func (cs *clientSuite) TestStartCanFail(c *C) {932func (cs *clientSuite) TestStartCanFail(c *C) {
@@ -951,3 +971,33 @@
951 c.Assert(err, NotNil)971 c.Assert(err, NotNil)
952 c.Check(cs.log.Captured(), Matches, "(?msi).*showing notification: no way$")972 c.Check(cs.log.Captured(), Matches, "(?msi).*showing notification: no way$")
953}973}
974
975/*****************************************************************
976 getAuthorization() tests
977******************************************************************/
978
979func (cs *clientSuite) TestGetAuthorizationIgnoresErrors(c *C) {
980 cli := NewPushClient(cs.configPath, cs.leveldbPath)
981 cli.configure()
982 cli.config.AuthHelper = "/no/such/executable"
983
984 c.Check(cli.getAuthorization("xyzzy://"), Equals, "")
985}
986
987func (cs *clientSuite) TestGetAuthorizationGetsIt(c *C) {
988 cli := NewPushClient(cs.configPath, cs.leveldbPath)
989 cli.configure()
990 cli.config.AuthHelper = "../scripts/dummyauth.sh"
991
992 c.Check(cli.getAuthorization("xyzzy://"), Equals, "hello xyzzy://")
993}
994
995func (cs *clientSuite) TestGetAuthorizationWorksIfUnsetOrNil(c *C) {
996 cli := NewPushClient(cs.configPath, cs.leveldbPath)
997 cli.log = cs.log
998
999 c.Assert(cli.config, NotNil)
1000 c.Check(cli.getAuthorization("xyzzy://"), Equals, "")
1001 cli.configure()
1002 c.Check(cli.getAuthorization("xyzzy://"), Equals, "")
1003}
9541004
=== added file 'client/service/common.go'
--- client/service/common.go 1970-01-01 00:00:00 +0000
+++ client/service/common.go 2014-06-20 13:02:58 +0000
@@ -0,0 +1,99 @@
1/*
2 Copyright 2014 Canonical Ltd.
3
4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published
6 by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranties of
10 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17// package service implements the dbus-level service with which client
18// applications are expected to interact.
19package service
20
21import (
22 "errors"
23 "sync"
24
25 "launchpad.net/ubuntu-push/bus"
26 "launchpad.net/ubuntu-push/logger"
27)
28
29type DBusService struct {
30 lock sync.RWMutex
31 state ServiceState
32 Log logger.Logger
33 Bus bus.Endpoint
34}
35
36// the service can be in a numnber of states
37type ServiceState uint8
38
39const (
40 StateUnknown ServiceState = iota
41 StateRunning // Start() has been successfully called
42 StateFinished // Stop() has been successfully called
43)
44
45var (
46 NotConfigured = errors.New("not configured")
47 AlreadyStarted = errors.New("already started")
48 BadArgCount = errors.New("Wrong number of arguments")
49 BadArgType = errors.New("Bad argument type")
50)
51
52// IsRunning() returns whether the service's state is StateRunning
53func (svc *DBusService) IsRunning() bool {
54 svc.lock.RLock()
55 defer svc.lock.RUnlock()
56 return svc.state == StateRunning
57}
58
59// Start() dials the bus, grab the name, and listens for method calls.
60func (svc *DBusService) Start(dispatchMap bus.DispatchMap, busAddr bus.Address) error {
61 svc.lock.Lock()
62 defer svc.lock.Unlock()
63 if svc.state != StateUnknown {
64 return AlreadyStarted
65 }
66 if svc.Log == nil || svc.Bus == nil {
67 return NotConfigured
68 }
69 err := svc.Bus.Dial()
70 if err != nil {
71 return err
72 }
73 ch := svc.Bus.GrabName(true)
74 log := svc.Log
75 go func() {
76 for err := range ch {
77 if !svc.IsRunning() {
78 break
79 }
80 if err != nil {
81 log.Fatalf("name channel for %s got: %v",
82 busAddr.Name, err)
83 }
84 }
85 }()
86 svc.Bus.WatchMethod(dispatchMap, svc)
87 svc.state = StateRunning
88 return nil
89}
90
91// Stop() closes the bus and sets the state to StateFinished
92func (svc *DBusService) Stop() {
93 svc.lock.Lock()
94 defer svc.lock.Unlock()
95 if svc.Bus != nil {
96 svc.Bus.Close()
97 }
98 svc.state = StateFinished
99}
0100
=== added file 'client/service/postal.go'
--- client/service/postal.go 1970-01-01 00:00:00 +0000
+++ client/service/postal.go 2014-06-20 13:02:58 +0000
@@ -0,0 +1,122 @@
1/*
2 Copyright 2013-2014 Canonical Ltd.
3
4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published
6 by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranties of
10 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17package service
18
19import (
20 "strings"
21
22 "launchpad.net/ubuntu-push/bus"
23 "launchpad.net/ubuntu-push/logger"
24 "launchpad.net/ubuntu-push/nih"
25)
26
27// PostalService is the dbus api
28type PostalService struct {
29 DBusService
30 mbox map[string][]string
31 msgHandler func([]byte) error
32}
33
34var (
35 PostalServiceBusAddress = bus.Address{
36 Interface: "com.ubuntu.Postal",
37 Path: "/com/ubuntu/Postal/*",
38 Name: "com.ubuntu.Postal",
39 }
40)
41
42// NewPostalService() builds a new service and returns it.
43func NewPostalService(bus bus.Endpoint, log logger.Logger) *PostalService {
44 var svc = &PostalService{}
45 svc.Log = log
46 svc.Bus = bus
47 return svc
48}
49
50// SetMessageHandler() sets the message-handling callback
51func (svc *PostalService) SetMessageHandler(callback func([]byte) error) {
52 svc.lock.RLock()
53 defer svc.lock.RUnlock()
54 svc.msgHandler = callback
55}
56
57// GetMessageHandler() returns the (possibly nil) messaging handler callback
58func (svc *PostalService) GetMessageHandler() func([]byte) error {
59 svc.lock.RLock()
60 defer svc.lock.RUnlock()
61 return svc.msgHandler
62}
63
64// Start() dials the bus, grab the name, and listens for method calls.
65func (svc *PostalService) Start() error {
66 return svc.DBusService.Start(bus.DispatchMap{
67 "Notifications": svc.notifications,
68 "Inject": svc.inject,
69 }, PostalServiceBusAddress)
70}
71
72func (svc *PostalService) notifications(path string, args, _ []interface{}) ([]interface{}, error) {
73 if len(args) != 0 {
74 return nil, BadArgCount
75 }
76 appname := string(nih.Unquote([]byte(path[strings.LastIndex(path, "/")+1:])))
77
78 svc.lock.Lock()
79 defer svc.lock.Unlock()
80
81 if svc.mbox == nil {
82 return []interface{}{[]string(nil)}, nil
83 }
84 msgs := svc.mbox[appname]
85 delete(svc.mbox, appname)
86
87 return []interface{}{msgs}, nil
88}
89
90func (svc *PostalService) inject(path string, args, _ []interface{}) ([]interface{}, error) {
91 if len(args) != 1 {
92 return nil, BadArgCount
93 }
94 notif, ok := args[0].(string)
95 if !ok {
96 return nil, BadArgType
97 }
98 appname := string(nih.Unquote([]byte(path[strings.LastIndex(path, "/")+1:])))
99
100 return nil, svc.Inject(appname, notif)
101}
102
103// Inject() signals to an application over dbus that a notification
104// has arrived.
105func (svc *PostalService) Inject(appname string, notif string) error {
106 svc.lock.Lock()
107 defer svc.lock.Unlock()
108 if svc.mbox == nil {
109 svc.mbox = make(map[string][]string)
110 }
111 svc.mbox[appname] = append(svc.mbox[appname], notif)
112 if svc.msgHandler != nil {
113 err := svc.msgHandler([]byte(notif))
114 if err != nil {
115 svc.DBusService.Log.Errorf("msgHandler returned %v", err)
116 return err
117 }
118 svc.DBusService.Log.Debugf("call to msgHandler successful")
119 }
120
121 return svc.Bus.Signal("Notification", []interface{}{appname})
122}
0123
=== added file 'client/service/postal_test.go'
--- client/service/postal_test.go 1970-01-01 00:00:00 +0000
+++ client/service/postal_test.go 2014-06-20 13:02:58 +0000
@@ -0,0 +1,191 @@
1/*
2 Copyright 2014 Canonical Ltd.
3
4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published
6 by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranties of
10 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17package service
18
19import (
20 "errors"
21
22 . "launchpad.net/gocheck"
23
24 "launchpad.net/ubuntu-push/bus"
25 testibus "launchpad.net/ubuntu-push/bus/testing"
26 "launchpad.net/ubuntu-push/logger"
27 helpers "launchpad.net/ubuntu-push/testing"
28 "launchpad.net/ubuntu-push/testing/condition"
29)
30
31type postalSuite struct {
32 log logger.Logger
33 bus bus.Endpoint
34}
35
36var _ = Suite(&postalSuite{})
37
38func (ss *postalSuite) SetUpTest(c *C) {
39 ss.log = helpers.NewTestLogger(c, "debug")
40 ss.bus = testibus.NewTestingEndpoint(condition.Work(true), nil)
41}
42
43func (ss *postalSuite) TestStart(c *C) {
44 svc := NewPostalService(ss.bus, ss.log)
45 c.Check(svc.IsRunning(), Equals, false)
46 c.Check(svc.Start(), IsNil)
47 c.Check(svc.IsRunning(), Equals, true)
48 svc.Stop()
49}
50
51func (ss *postalSuite) TestStartTwice(c *C) {
52 svc := NewPostalService(ss.bus, ss.log)
53 c.Check(svc.Start(), IsNil)
54 c.Check(svc.Start(), Equals, AlreadyStarted)
55 svc.Stop()
56}
57
58func (ss *postalSuite) TestStartNoLog(c *C) {
59 svc := NewPostalService(ss.bus, nil)
60 c.Check(svc.Start(), Equals, NotConfigured)
61}
62
63func (ss *postalSuite) TestStartNoBus(c *C) {
64 svc := NewPostalService(nil, ss.log)
65 c.Check(svc.Start(), Equals, NotConfigured)
66}
67
68func (ss *postalSuite) TestStartFailsOnBusDialFailure(c *C) {
69 bus := testibus.NewTestingEndpoint(condition.Work(false), nil)
70 svc := NewPostalService(bus, ss.log)
71 c.Check(svc.Start(), ErrorMatches, `.*(?i)cond said no.*`)
72 svc.Stop()
73}
74
75func (ss *postalSuite) TestStartGrabsName(c *C) {
76 svc := NewPostalService(ss.bus, ss.log)
77 c.Assert(svc.Start(), IsNil)
78 callArgs := testibus.GetCallArgs(ss.bus)
79 defer svc.Stop()
80 c.Assert(callArgs, NotNil)
81 c.Check(callArgs[0].Member, Equals, "::GrabName")
82}
83
84func (ss *postalSuite) TestStopClosesBus(c *C) {
85 svc := NewPostalService(ss.bus, ss.log)
86 c.Assert(svc.Start(), IsNil)
87 svc.Stop()
88 callArgs := testibus.GetCallArgs(ss.bus)
89 c.Assert(callArgs, NotNil)
90 c.Check(callArgs[len(callArgs)-1].Member, Equals, "::Close")
91}
92
93//
94// Injection tests
95
96func (ss *postalSuite) TestInjectWorks(c *C) {
97 svc := NewPostalService(ss.bus, ss.log)
98 rvs, err := svc.inject("/hello", []interface{}{"world"}, nil)
99 c.Assert(err, IsNil)
100 c.Check(rvs, IsNil)
101 rvs, err = svc.inject("/hello", []interface{}{"there"}, nil)
102 c.Assert(err, IsNil)
103 c.Check(rvs, IsNil)
104 c.Assert(svc.mbox, HasLen, 1)
105 c.Assert(svc.mbox["hello"], HasLen, 2)
106 c.Check(svc.mbox["hello"][0], Equals, "world")
107 c.Check(svc.mbox["hello"][1], Equals, "there")
108
109 // and check it fired the right signal (twice)
110 callArgs := testibus.GetCallArgs(ss.bus)
111 c.Assert(callArgs, HasLen, 2)
112 c.Check(callArgs[0].Member, Equals, "::Signal")
113 c.Check(callArgs[0].Args, DeepEquals, []interface{}{"Notification", []interface{}{"hello"}})
114 c.Check(callArgs[1], DeepEquals, callArgs[0])
115}
116
117func (ss *postalSuite) TestInjectFailsIfInjectFails(c *C) {
118 bus := testibus.NewTestingEndpoint(condition.Work(true),
119 condition.Work(false))
120 svc := NewPostalService(bus, ss.log)
121 svc.SetMessageHandler(func([]byte) error { return errors.New("fail") })
122 _, err := svc.inject("/hello", []interface{}{"xyzzy"}, nil)
123 c.Check(err, NotNil)
124}
125
126func (ss *postalSuite) TestInjectFailsIfBadArgs(c *C) {
127 for i, s := range []struct {
128 args []interface{}
129 errt error
130 }{
131 {nil, BadArgCount},
132 {[]interface{}{}, BadArgCount},
133 {[]interface{}{1}, BadArgType},
134 {[]interface{}{1, 2}, BadArgCount},
135 } {
136 reg, err := new(PostalService).inject("", s.args, nil)
137 c.Check(reg, IsNil, Commentf("iteration #%d", i))
138 c.Check(err, Equals, s.errt, Commentf("iteration #%d", i))
139 }
140}
141
142//
143// Notifications tests
144func (ss *postalSuite) TestNotificationsWorks(c *C) {
145 svc := NewPostalService(ss.bus, ss.log)
146 nots, err := svc.notifications("/hello", nil, nil)
147 c.Assert(err, IsNil)
148 c.Assert(nots, NotNil)
149 c.Assert(nots, HasLen, 1)
150 c.Check(nots[0], HasLen, 0)
151 if svc.mbox == nil {
152 svc.mbox = make(map[string][]string)
153 }
154 svc.mbox["hello"] = append(svc.mbox["hello"], "this", "thing")
155 nots, err = svc.notifications("/hello", nil, nil)
156 c.Assert(err, IsNil)
157 c.Assert(nots, NotNil)
158 c.Assert(nots, HasLen, 1)
159 c.Check(nots[0], DeepEquals, []string{"this", "thing"})
160}
161
162func (ss *postalSuite) TestNotificationsFailsIfBadArgs(c *C) {
163 reg, err := new(PostalService).notifications("/foo", []interface{}{1}, nil)
164 c.Check(reg, IsNil)
165 c.Check(err, Equals, BadArgCount)
166}
167
168func (ss *postalSuite) TestMessageHandler(c *C) {
169 svc := new(PostalService)
170 c.Assert(svc.msgHandler, IsNil)
171 var ext = []byte{}
172 e := errors.New("Hello")
173 f := func(s []byte) error { ext = s; return e }
174 c.Check(svc.GetMessageHandler(), IsNil)
175 svc.SetMessageHandler(f)
176 c.Check(svc.GetMessageHandler(), NotNil)
177 c.Check(svc.msgHandler([]byte("37")), Equals, e)
178 c.Check(ext, DeepEquals, []byte("37"))
179}
180
181func (ss *postalSuite) TestInjectCallsMessageHandler(c *C) {
182 var ext = []byte{}
183 svc := NewPostalService(ss.bus, ss.log)
184 f := func(s []byte) error { ext = s; return nil }
185 svc.SetMessageHandler(f)
186 c.Check(svc.Inject("stuff", "{}"), IsNil)
187 c.Check(ext, DeepEquals, []byte("{}"))
188 err := errors.New("ouch")
189 svc.SetMessageHandler(func([]byte) error { return err })
190 c.Check(svc.Inject("stuff", "{}"), Equals, err)
191}
0192
=== modified file 'client/service/service.go'
--- client/service/service.go 2014-05-21 10:03:18 +0000
+++ client/service/service.go 2014-06-20 13:02:58 +0000
@@ -14,196 +14,84 @@
14 with this program. If not, see <http://www.gnu.org/licenses/>.14 with this program. If not, see <http://www.gnu.org/licenses/>.
15*/15*/
1616
17// package service implements the dbus-level service with which client
18// applications are expected to interact.
19package service17package service
2018
21import (19import (
22 "errors"
23 "os"20 "os"
24 "sync"21 "strings"
2522
26 "launchpad.net/ubuntu-push/bus"23 "launchpad.net/ubuntu-push/bus"
27 "launchpad.net/ubuntu-push/logger"24 "launchpad.net/ubuntu-push/logger"
25 "launchpad.net/ubuntu-push/nih"
28)26)
2927
30// Service is the dbus api28// PushService is the dbus api
31type Service struct {29type PushService struct {
32 lock sync.RWMutex30 DBusService
33 state ServiceState31 regURL string
34 mbox map[string][]string32 authGetter func(string) string
35 msgHandler func([]byte) error
36 Log logger.Logger
37 Bus bus.Endpoint
38}33}
3934
40// the service can be in a numnber of states
41type ServiceState uint8
42
43const (
44 StateUnknown ServiceState = iota
45 StateRunning // Start() has been successfully called
46 StateFinished // Stop() has been successfully called
47)
48
49var (35var (
50 NotConfigured = errors.New("not configured")36 PushServiceBusAddress = bus.Address{
51 AlreadyStarted = errors.New("already started")
52 BusAddress = bus.Address{
53 Interface: "com.ubuntu.PushNotifications",37 Interface: "com.ubuntu.PushNotifications",
54 Path: "/com/ubuntu/PushNotifications",38 Path: "/com/ubuntu/PushNotifications/*",
55 Name: "com.ubuntu.PushNotifications",39 Name: "com.ubuntu.PushNotifications",
56 }40 }
57)41)
5842
59// NewService() builds a new service and returns it.43// NewPushService() builds a new service and returns it.
60func NewService(bus bus.Endpoint, log logger.Logger) *Service {44func NewPushService(bus bus.Endpoint, log logger.Logger) *PushService {
61 return &Service{Log: log, Bus: bus}45 var svc = &PushService{}
62}46 svc.Log = log
6347 svc.Bus = bus
64// SetMessageHandler() sets the message-handling callback48 return svc
65func (svc *Service) SetMessageHandler(callback func([]byte) error) {49}
66 svc.lock.Lock()50
67 defer svc.lock.Unlock()51// SetRegistrationURL() sets the registration url for the service
68 svc.msgHandler = callback52func (svc *PushService) SetRegistrationURL(url string) {
69}53 svc.lock.Lock()
7054 defer svc.lock.Unlock()
71// GetMessageHandler() returns the (possibly nil) messaging handler callback55 svc.regURL = url
72func (svc *Service) GetMessageHandler() func([]byte) error {56}
73 svc.lock.RLock()57
74 defer svc.lock.RUnlock()58// SetAuthGetter() sets the authorization getter for the service
75 return svc.msgHandler59func (svc *PushService) SetAuthGetter(authGetter func(string) string) {
76}60 svc.lock.Lock()
7761 defer svc.lock.Unlock()
78// IsRunning() returns whether the service's state is StateRunning62 svc.authGetter = authGetter
79func (svc *Service) IsRunning() bool {63}
80 svc.lock.RLock()64
81 defer svc.lock.RUnlock()65// GetRegistrationAuthorization() returns the authorization header for
82 return svc.state == StateRunning66// POSTing to the registration HTTP endpoint
67func (svc *PushService) GetRegistrationAuthorization() string {
68 svc.lock.RLock()
69 defer svc.lock.RUnlock()
70 if svc.authGetter != nil && svc.regURL != "" {
71 return svc.authGetter(svc.regURL)
72 } else {
73 return ""
74 }
83}75}
8476
85// Start() dials the bus, grab the name, and listens for method calls.77// Start() dials the bus, grab the name, and listens for method calls.
86func (svc *Service) Start() error {78func (svc *PushService) Start() error {
87 svc.lock.Lock()79 return svc.DBusService.Start(bus.DispatchMap{
88 defer svc.lock.Unlock()80 "Register": svc.register,
89 if svc.state != StateUnknown {81 }, PushServiceBusAddress)
90 return AlreadyStarted82}
91 }83
92 if svc.Log == nil || svc.Bus == nil {84func (svc *PushService) register(path string, args, _ []interface{}) ([]interface{}, error) {
93 return NotConfigured85 if len(args) != 0 {
94 }
95 err := svc.Bus.Dial()
96 if err != nil {
97 return err
98 }
99 ch := svc.Bus.GrabName(true)
100 log := svc.Log
101 go func() {
102 for err := range ch {
103 if !svc.IsRunning() {
104 break
105 }
106 if err != nil {
107 log.Fatalf("name channel for %s got: %v",
108 BusAddress.Name, err)
109 }
110 }
111 }()
112 svc.Bus.WatchMethod(bus.DispatchMap{
113 "Register": svc.register,
114 "Notifications": svc.notifications,
115 "Inject": svc.inject,
116 }, svc)
117 svc.state = StateRunning
118 return nil
119}
120
121// Stop() closes the bus and sets the state to StateFinished
122func (svc *Service) Stop() {
123 svc.lock.Lock()
124 defer svc.lock.Unlock()
125 if svc.Bus != nil {
126 svc.Bus.Close()
127 }
128 svc.state = StateFinished
129}
130
131var (
132 BadArgCount = errors.New("Wrong number of arguments")
133 BadArgType = errors.New("Bad argument type")
134)
135
136func (svc *Service) register(args []interface{}, _ []interface{}) ([]interface{}, error) {
137 if len(args) != 1 {
138 return nil, BadArgCount86 return nil, BadArgCount
139 }87 }
140 appname, ok := args[0].(string)88 raw_appname := path[strings.LastIndex(path, "/")+1:]
141 if !ok {89 appname := string(nih.Unquote([]byte(raw_appname)))
142 return nil, BadArgType
143 }
14490
145 rv := os.Getenv("PUSH_REG_" + appname)91 rv := os.Getenv("PUSH_REG_" + raw_appname)
146 if rv == "" {92 if rv == "" {
147 rv = "this-is-an-opaque-block-of-random-bits-i-promise"93 rv = appname + "::this-is-an-opaque-block-of-random-bits-i-promise"
148 }94 }
14995
150 return []interface{}{rv}, nil96 return []interface{}{rv}, nil
151}97}
152
153func (svc *Service) notifications(args []interface{}, _ []interface{}) ([]interface{}, error) {
154 if len(args) != 1 {
155 return nil, BadArgCount
156 }
157 appname, ok := args[0].(string)
158 if !ok {
159 return nil, BadArgType
160 }
161
162 svc.lock.Lock()
163 defer svc.lock.Unlock()
164
165 if svc.mbox == nil {
166 return []interface{}{[]string(nil)}, nil
167 }
168 msgs := svc.mbox[appname]
169 delete(svc.mbox, appname)
170
171 return []interface{}{msgs}, nil
172}
173
174func (svc *Service) inject(args []interface{}, _ []interface{}) ([]interface{}, error) {
175 if len(args) != 2 {
176 return nil, BadArgCount
177 }
178 appname, ok := args[0].(string)
179 if !ok {
180 return nil, BadArgType
181 }
182 notif, ok := args[1].(string)
183 if !ok {
184 return nil, BadArgType
185 }
186
187 return nil, svc.Inject(appname, notif)
188}
189
190// Inject() signals to an application over dbus that a notification
191// has arrived.
192func (svc *Service) Inject(appname string, notif string) error {
193 svc.lock.Lock()
194 defer svc.lock.Unlock()
195 if svc.mbox == nil {
196 svc.mbox = make(map[string][]string)
197 }
198 svc.mbox[appname] = append(svc.mbox[appname], notif)
199 if svc.msgHandler != nil {
200 err := svc.msgHandler([]byte(notif))
201 if err != nil {
202 svc.Log.Errorf("msgHandler returned %v", err)
203 return err
204 }
205 svc.Log.Debugf("call to msgHandler successful")
206 }
207
208 return svc.Bus.Signal("Notification", []interface{}{appname})
209}
21098
=== modified file 'client/service/service_test.go'
--- client/service/service_test.go 2014-05-21 10:05:24 +0000
+++ client/service/service_test.go 2014-06-20 13:02:58 +0000
@@ -17,7 +17,7 @@
17package service17package service
1818
19import (19import (
20 "errors"20 "fmt"
21 "os"21 "os"
22 "testing"22 "testing"
2323
@@ -45,7 +45,7 @@
45}45}
4646
47func (ss *serviceSuite) TestStart(c *C) {47func (ss *serviceSuite) TestStart(c *C) {
48 svc := NewService(ss.bus, ss.log)48 svc := NewPushService(ss.bus, ss.log)
49 c.Check(svc.IsRunning(), Equals, false)49 c.Check(svc.IsRunning(), Equals, false)
50 c.Check(svc.Start(), IsNil)50 c.Check(svc.Start(), IsNil)
51 c.Check(svc.IsRunning(), Equals, true)51 c.Check(svc.IsRunning(), Equals, true)
@@ -53,31 +53,31 @@
53}53}
5454
55func (ss *serviceSuite) TestStartTwice(c *C) {55func (ss *serviceSuite) TestStartTwice(c *C) {
56 svc := NewService(ss.bus, ss.log)56 svc := NewPushService(ss.bus, ss.log)
57 c.Check(svc.Start(), IsNil)57 c.Check(svc.Start(), IsNil)
58 c.Check(svc.Start(), Equals, AlreadyStarted)58 c.Check(svc.Start(), Equals, AlreadyStarted)
59 svc.Stop()59 svc.Stop()
60}60}
6161
62func (ss *serviceSuite) TestStartNoLog(c *C) {62func (ss *serviceSuite) TestStartNoLog(c *C) {
63 svc := NewService(ss.bus, nil)63 svc := NewPushService(ss.bus, nil)
64 c.Check(svc.Start(), Equals, NotConfigured)64 c.Check(svc.Start(), Equals, NotConfigured)
65}65}
6666
67func (ss *serviceSuite) TestStartNoBus(c *C) {67func (ss *serviceSuite) TestStartNoBus(c *C) {
68 svc := NewService(nil, ss.log)68 svc := NewPushService(nil, ss.log)
69 c.Check(svc.Start(), Equals, NotConfigured)69 c.Check(svc.Start(), Equals, NotConfigured)
70}70}
7171
72func (ss *serviceSuite) TestStartFailsOnBusDialFailure(c *C) {72func (ss *serviceSuite) TestStartFailsOnBusDialFailure(c *C) {
73 bus := testibus.NewTestingEndpoint(condition.Work(false), nil)73 bus := testibus.NewTestingEndpoint(condition.Work(false), nil)
74 svc := NewService(bus, ss.log)74 svc := NewPushService(bus, ss.log)
75 c.Check(svc.Start(), ErrorMatches, `.*(?i)cond said no.*`)75 c.Check(svc.Start(), ErrorMatches, `.*(?i)cond said no.*`)
76 svc.Stop()76 svc.Stop()
77}77}
7878
79func (ss *serviceSuite) TestStartGrabsName(c *C) {79func (ss *serviceSuite) TestStartGrabsName(c *C) {
80 svc := NewService(ss.bus, ss.log)80 svc := NewPushService(ss.bus, ss.log)
81 c.Assert(svc.Start(), IsNil)81 c.Assert(svc.Start(), IsNil)
82 callArgs := testibus.GetCallArgs(ss.bus)82 callArgs := testibus.GetCallArgs(ss.bus)
83 defer svc.Stop()83 defer svc.Stop()
@@ -86,7 +86,7 @@
86}86}
8787
88func (ss *serviceSuite) TestStopClosesBus(c *C) {88func (ss *serviceSuite) TestStopClosesBus(c *C) {
89 svc := NewService(ss.bus, ss.log)89 svc := NewPushService(ss.bus, ss.log)
90 c.Assert(svc.Start(), IsNil)90 c.Assert(svc.Start(), IsNil)
91 svc.Stop()91 svc.Stop()
92 callArgs := testibus.GetCallArgs(ss.bus)92 callArgs := testibus.GetCallArgs(ss.bus)
@@ -96,24 +96,45 @@
9696
97// registration tests97// registration tests
9898
99func (ss *serviceSuite) TestSetRegURLWorks(c *C) {
100 svc := NewPushService(ss.bus, ss.log)
101 c.Check(svc.regURL, Equals, "")
102 svc.SetRegistrationURL("xyzzy://")
103 c.Check(svc.regURL, Equals, "xyzzy://")
104}
105
106func (ss *serviceSuite) TestSetAuthGetterWorks(c *C) {
107 svc := NewPushService(ss.bus, ss.log)
108 c.Check(svc.authGetter, IsNil)
109 f := func(string) string { return "" }
110 svc.SetAuthGetter(f)
111 c.Check(fmt.Sprintf("%#v", svc.authGetter), Equals, fmt.Sprintf("%#v", f))
112}
113
114func (ss *serviceSuite) TestGetRegAuthWorks(c *C) {
115 svc := NewPushService(ss.bus, ss.log)
116 svc.SetRegistrationURL("xyzzy://")
117 ch := make(chan string, 1)
118 f := func(s string) string { ch <- s; return "Auth " + s }
119 svc.SetAuthGetter(f)
120 c.Check(svc.GetRegistrationAuthorization(), Equals, "Auth xyzzy://")
121 c.Assert(len(ch), Equals, 1)
122 c.Check(<-ch, Equals, "xyzzy://")
123}
124
125func (ss *serviceSuite) TestGetRegAuthDoesNotPanic(c *C) {
126 svc := NewPushService(ss.bus, ss.log)
127 c.Check(svc.GetRegistrationAuthorization(), Equals, "")
128}
129
99func (ss *serviceSuite) TestRegistrationFailsIfBadArgs(c *C) {130func (ss *serviceSuite) TestRegistrationFailsIfBadArgs(c *C) {
100 for i, s := range []struct {131 reg, err := new(PushService).register("", []interface{}{1}, nil)
101 args []interface{}132 c.Check(reg, IsNil)
102 errt error133 c.Check(err, Equals, BadArgCount)
103 }{
104 {nil, BadArgCount}, // no args
105 {[]interface{}{}, BadArgCount}, // still no args
106 {[]interface{}{42}, BadArgType}, // bad arg type
107 {[]interface{}{1, 2}, BadArgCount}, // too many args
108 } {
109 reg, err := new(Service).register(s.args, nil)
110 c.Check(reg, IsNil, Commentf("iteration #%d", i))
111 c.Check(err, Equals, s.errt, Commentf("iteration #%d", i))
112 }
113}134}
114135
115func (ss *serviceSuite) TestRegistrationWorks(c *C) {136func (ss *serviceSuite) TestRegistrationWorks(c *C) {
116 reg, err := new(Service).register([]interface{}{"this"}, nil)137 reg, err := new(PushService).register("/this", nil, nil)
117 c.Assert(reg, HasLen, 1)138 c.Assert(reg, HasLen, 1)
118 regs, ok := reg[0].(string)139 regs, ok := reg[0].(string)
119 c.Check(ok, Equals, true)140 c.Check(ok, Equals, true)
@@ -125,123 +146,10 @@
125 os.Setenv("PUSH_REG_stuff", "42")146 os.Setenv("PUSH_REG_stuff", "42")
126 defer os.Setenv("PUSH_REG_stuff", "")147 defer os.Setenv("PUSH_REG_stuff", "")
127148
128 reg, err := new(Service).register([]interface{}{"stuff"}, nil)149 reg, err := new(PushService).register("/stuff", nil, nil)
129 c.Assert(reg, HasLen, 1)150 c.Assert(reg, HasLen, 1)
130 regs, ok := reg[0].(string)151 regs, ok := reg[0].(string)
131 c.Check(ok, Equals, true)152 c.Check(ok, Equals, true)
132 c.Check(regs, Equals, "42")153 c.Check(regs, Equals, "42")
133 c.Check(err, IsNil)154 c.Check(err, IsNil)
134}155}
135
136//
137// Injection tests
138
139func (ss *serviceSuite) TestInjectWorks(c *C) {
140 svc := NewService(ss.bus, ss.log)
141 rvs, err := svc.inject([]interface{}{"hello", "world"}, nil)
142 c.Assert(err, IsNil)
143 c.Check(rvs, IsNil)
144 rvs, err = svc.inject([]interface{}{"hello", "there"}, nil)
145 c.Assert(err, IsNil)
146 c.Check(rvs, IsNil)
147 c.Assert(svc.mbox, HasLen, 1)
148 c.Assert(svc.mbox["hello"], HasLen, 2)
149 c.Check(svc.mbox["hello"][0], Equals, "world")
150 c.Check(svc.mbox["hello"][1], Equals, "there")
151
152 // and check it fired the right signal (twice)
153 callArgs := testibus.GetCallArgs(ss.bus)
154 c.Assert(callArgs, HasLen, 2)
155 c.Check(callArgs[0].Member, Equals, "::Signal")
156 c.Check(callArgs[0].Args, DeepEquals, []interface{}{"Notification", []interface{}{"hello"}})
157 c.Check(callArgs[1], DeepEquals, callArgs[0])
158}
159
160func (ss *serviceSuite) TestInjectFailsIfInjectFails(c *C) {
161 bus := testibus.NewTestingEndpoint(condition.Work(true),
162 condition.Work(false))
163 svc := NewService(bus, ss.log)
164 svc.SetMessageHandler(func([]byte) error { return errors.New("fail") })
165 _, err := svc.inject([]interface{}{"hello", "xyzzy"}, nil)
166 c.Check(err, NotNil)
167}
168
169func (ss *serviceSuite) TestInjectFailsIfBadArgs(c *C) {
170 for i, s := range []struct {
171 args []interface{}
172 errt error
173 }{
174 {nil, BadArgCount},
175 {[]interface{}{}, BadArgCount},
176 {[]interface{}{1}, BadArgCount},
177 {[]interface{}{1, 2}, BadArgType},
178 {[]interface{}{"1", 2}, BadArgType},
179 {[]interface{}{1, "2"}, BadArgType},
180 {[]interface{}{1, 2, 3}, BadArgCount},
181 } {
182 reg, err := new(Service).inject(s.args, nil)
183 c.Check(reg, IsNil, Commentf("iteration #%d", i))
184 c.Check(err, Equals, s.errt, Commentf("iteration #%d", i))
185 }
186}
187
188//
189// Notifications tests
190func (ss *serviceSuite) TestNotificationsWorks(c *C) {
191 svc := NewService(ss.bus, ss.log)
192 nots, err := svc.notifications([]interface{}{"hello"}, nil)
193 c.Assert(err, IsNil)
194 c.Assert(nots, NotNil)
195 c.Assert(nots, HasLen, 1)
196 c.Check(nots[0], HasLen, 0)
197 if svc.mbox == nil {
198 svc.mbox = make(map[string][]string)
199 }
200 svc.mbox["hello"] = append(svc.mbox["hello"], "this", "thing")
201 nots, err = svc.notifications([]interface{}{"hello"}, nil)
202 c.Assert(err, IsNil)
203 c.Assert(nots, NotNil)
204 c.Assert(nots, HasLen, 1)
205 c.Check(nots[0], DeepEquals, []string{"this", "thing"})
206}
207
208func (ss *serviceSuite) TestNotificationsFailsIfBadArgs(c *C) {
209 for i, s := range []struct {
210 args []interface{}
211 errt error
212 }{
213 {nil, BadArgCount}, // no args
214 {[]interface{}{}, BadArgCount}, // still no args
215 {[]interface{}{42}, BadArgType}, // bad arg type
216 {[]interface{}{1, 2}, BadArgCount}, // too many args
217 } {
218 reg, err := new(Service).notifications(s.args, nil)
219 c.Check(reg, IsNil, Commentf("iteration #%d", i))
220 c.Check(err, Equals, s.errt, Commentf("iteration #%d", i))
221 }
222}
223
224func (ss *serviceSuite) TestMessageHandler(c *C) {
225 svc := new(Service)
226 c.Assert(svc.msgHandler, IsNil)
227 var ext = []byte{}
228 e := errors.New("Hello")
229 f := func(s []byte) error { ext = s; return e }
230 c.Check(svc.GetMessageHandler(), IsNil)
231 svc.SetMessageHandler(f)
232 c.Check(svc.GetMessageHandler(), NotNil)
233 c.Check(svc.msgHandler([]byte("37")), Equals, e)
234 c.Check(ext, DeepEquals, []byte("37"))
235}
236
237func (ss *serviceSuite) TestInjectCallsMessageHandler(c *C) {
238 var ext = []byte{}
239 svc := NewService(ss.bus, ss.log)
240 f := func(s []byte) error { ext = s; return nil }
241 svc.SetMessageHandler(f)
242 c.Check(svc.Inject("stuff", "{}"), IsNil)
243 c.Check(ext, DeepEquals, []byte("{}"))
244 err := errors.New("ouch")
245 svc.SetMessageHandler(func([]byte) error { return err })
246 c.Check(svc.Inject("stuff", "{}"), Equals, err)
247}
248156
=== modified file 'client/session/session.go'
--- client/session/session.go 2014-05-23 05:59:38 +0000
+++ client/session/session.go 2014-06-20 13:02:58 +0000
@@ -26,7 +26,6 @@
26 "fmt"26 "fmt"
27 "math/rand"27 "math/rand"
28 "net"28 "net"
29 "os/exec"
30 "strings"29 "strings"
31 "sync"30 "sync"
32 "sync/atomic"31 "sync/atomic"
@@ -87,7 +86,8 @@
87 ExpectAllRepairedTime time.Duration86 ExpectAllRepairedTime time.Duration
88 PEM []byte87 PEM []byte
89 Info map[string]interface{}88 Info map[string]interface{}
90 AuthHelper []string89 AuthGetter func(string) string
90 AuthURL string
91}91}
9292
93// ClientSession holds a client<->server session and its configuration.93// ClientSession holds a client<->server session and its configuration.
@@ -244,21 +244,10 @@
244// addAuthorization gets the authorization blob to send to the server244// addAuthorization gets the authorization blob to send to the server
245// and adds it to the session.245// and adds it to the session.
246func (sess *ClientSession) addAuthorization() error {246func (sess *ClientSession) addAuthorization() error {
247 sess.Log.Debugf("adding authorization")247 if sess.AuthGetter != nil {
248 // using a helper, for now at least248 sess.Log.Debugf("adding authorization")
249 if len(sess.AuthHelper) == 0 {249 sess.auth = sess.AuthGetter(sess.AuthURL)
250 // do nothing if helper is unset or empty250 }
251 return nil
252 }
253
254 auth, err := exec.Command(sess.AuthHelper[0], sess.AuthHelper[1:]...).Output()
255 if err != nil {
256 // For now we just log the error, as we don't want to block unauthorized users
257 sess.Log.Errorf("unable to get the authorization token from the account: %v", err)
258 } else {
259 sess.auth = strings.TrimSpace(string(auth))
260 }
261
262 return nil251 return nil
263}252}
264253
265254
=== modified file 'client/session/session_test.go'
--- client/session/session_test.go 2014-05-23 05:59:38 +0000
+++ client/session/session_test.go 2014-06-20 13:02:58 +0000
@@ -353,34 +353,21 @@
353****************************************************************/353****************************************************************/
354354
355func (cs *clientSessionSuite) TestAddAuthorizationAddsAuthorization(c *C) {355func (cs *clientSessionSuite) TestAddAuthorizationAddsAuthorization(c *C) {
356 sess := &ClientSession{Log: cs.log}356 url := "xyzzy://"
357 sess.AuthHelper = []string{"echo", "some auth"}357 sess := &ClientSession{Log: cs.log}
358 c.Assert(sess.auth, Equals, "")358 sess.AuthGetter = func(url string) string { return url + " auth'ed" }
359 err := sess.addAuthorization()359 sess.AuthURL = url
360 c.Assert(err, IsNil)360 c.Assert(sess.auth, Equals, "")
361 c.Check(sess.auth, Equals, "some auth")361 err := sess.addAuthorization()
362}362 c.Assert(err, IsNil)
363363 c.Check(sess.auth, Equals, "xyzzy:// auth'ed")
364func (cs *clientSessionSuite) TestAddAuthorizationIgnoresErrors(c *C) {364}
365 sess := &ClientSession{Log: cs.log}365
366 sess.AuthHelper = []string{"sh", "-c", "echo hello; false"}366func (cs *clientSessionSuite) TestAddAuthorizationSkipsIfUnset(c *C) {
367367 sess := &ClientSession{Log: cs.log}
368 c.Assert(sess.auth, Equals, "")368 sess.AuthGetter = nil
369 err := sess.addAuthorization()369 c.Assert(sess.auth, Equals, "")
370 c.Assert(err, IsNil)370 err := sess.addAuthorization()
371 c.Check(sess.auth, Equals, "")
372}
373
374func (cs *clientSessionSuite) TestAddAuthorizationSkipsIfUnsetOrNil(c *C) {
375 sess := &ClientSession{Log: cs.log}
376 sess.AuthHelper = nil
377 c.Assert(sess.auth, Equals, "")
378 err := sess.addAuthorization()
379 c.Assert(err, IsNil)
380 c.Check(sess.auth, Equals, "")
381
382 sess.AuthHelper = []string{}
383 err = sess.addAuthorization()
384 c.Assert(err, IsNil)371 c.Assert(err, IsNil)
385 c.Check(sess.auth, Equals, "")372 c.Check(sess.auth, Equals, "")
386}373}
387374
=== modified file 'debian/changelog'
--- debian/changelog 2014-06-05 09:42:22 +0000
+++ debian/changelog 2014-06-20 13:02:58 +0000
@@ -1,3 +1,28 @@
1ubuntu-push (0.31ubuntu1) UNRELEASED; urgency=medium
2
3 [ Samuele Pedroni ]
4 * Support registering tokens and sending notifications with a token
5 * Register script and scripts unicast support
6
7 [ Roberto Alsina ]
8 * Make signing-helper generate a HTTP header instead of a querystring,
9 and take a URL to sign.
10
11 [ John R. Lenton ]
12 * Switch dbus api to retrieve app name from dbus path.
13 * Move signing bits up from session to client, for reuse by service.
14 * Change AuthHelper to be a string; auth helper should now expect a
15 parameter (the url to sign). Added SessionURL to config.
16 * Adapt our whoopsie wrapper to whoopsie's now more correct behavior wrt
17 failing to get a mac address.
18 * Add registration_url to config; hook up auth bits and reg url to
19 client & service.
20
21 [ Guillermo Gonzalez ]
22 * Split DBus service into PushService and PostalService
23
24 -- John R. Lenton <john.lenton@canonical.com> Fri, 06 Jun 2014 12:02:56 +0100
25
1ubuntu-push (0.3+14.10.20140605-0ubuntu1) utopic; urgency=medium26ubuntu-push (0.3+14.10.20140605-0ubuntu1) utopic; urgency=medium
227
3 [ John Lenton ]28 [ John Lenton ]
429
=== modified file 'debian/config.json'
--- debian/config.json 2014-06-04 12:18:42 +0000
+++ debian/config.json 2014-06-20 13:02:58 +0000
@@ -1,5 +1,7 @@
1{1{
2 "auth_helper": [],2 "auth_helper": "/usr/lib/ubuntu-push-client/signing-helper",
3 "session_url": "https://push.ubuntu.com/",
4 "registration_url": "https://push.ubuntu.com/registration",
3 "connect_timeout": "20s",5 "connect_timeout": "20s",
4 "exchange_timeout": "30s",6 "exchange_timeout": "30s",
5 "hosts_cache_expiry": "12h",7 "hosts_cache_expiry": "12h",
68
=== modified file 'debian/rules'
--- debian/rules 2014-05-02 12:42:27 +0000
+++ debian/rules 2014-06-20 13:02:58 +0000
@@ -5,7 +5,6 @@
5export UBUNTU_PUSH_TEST_RESOURCES_ROOT := $(CURDIR)5export UBUNTU_PUSH_TEST_RESOURCES_ROOT := $(CURDIR)
66
7override_dh_auto_build:7override_dh_auto_build:
8 cd $$( find ./ -type d -regex '\./[^/]*/src/launchpad.net' -printf "%h\n" | head -n1)
9 dh_auto_build --buildsystem=golang8 dh_auto_build --buildsystem=golang
10 (cd signing-helper && cmake . && make)9 (cd signing-helper && cmake . && make)
1110
1211
=== modified file 'http13client/Makefile'
--- http13client/Makefile 2014-03-20 12:15:36 +0000
+++ http13client/Makefile 2014-06-20 13:02:58 +0000
@@ -20,7 +20,8 @@
2020
21prune:21prune:
22 rm -rf example_test.go filetransport*.go fs*.go race.go range_test.go \22 rm -rf example_test.go filetransport*.go fs*.go race.go range_test.go \
23 sniff*.go httptest httputil testdata triv.go jar.go status.go23 sniff*.go httptest httputil testdata triv.go jar.go status.go \
24 cookie_test.go
24 sed -i -e 's+"launchpad.net/ubuntu-push/http13client/+"net/http/+' *.go25 sed -i -e 's+"launchpad.net/ubuntu-push/http13client/+"net/http/+' *.go
2526
26fix:27fix:
2728
=== modified file 'http13client/_patches/empty_server.patch'
--- http13client/_patches/empty_server.patch 2014-03-19 23:13:58 +0000
+++ http13client/_patches/empty_server.patch 2014-06-20 13:02:58 +0000
@@ -1,6 +1,6 @@
1=== modified file 'http13client/serve_test.go'1=== modified file 'http13client/serve_test.go'
2--- http13client/serve_test.go 2014-03-19 21:38:56 +00002--- http13client/serve_test.go 2014-06-20 11:00:47 +0000
3+++ http13client/serve_test.go 2014-03-19 22:27:37 +00003+++ http13client/serve_test.go 2014-06-20 12:00:22 +0000
4@@ -2,60 +2,15 @@4@@ -2,60 +2,15 @@
5 // Use of this source code is governed by a BSD-style5 // Use of this source code is governed by a BSD-style
6 // license that can be found in the LICENSE file.6 // license that can be found in the LICENSE file.
@@ -62,7 +62,7 @@
62 62
63 func (a dummyAddr) Network() string {63 func (a dummyAddr) Network() string {
64 return string(a)64 return string(a)
65@@ -93,1289 +48,6 @@65@@ -93,1325 +48,6 @@
66 return nil66 return nil
67 }67 }
68 68
@@ -906,31 +906,50 @@
906-}906-}
907-907-
908-type serverExpectTest struct {908-type serverExpectTest struct {
909- contentLength int // of request body909- contentLength int // of request body
910- chunked bool
910- expectation string // e.g. "100-continue"911- expectation string // e.g. "100-continue"
911- readBody bool // whether handler should read the body (if false, sends StatusUnauthorized)912- readBody bool // whether handler should read the body (if false, sends StatusUnauthorized)
912- expectedResponse string // expected substring in first line of http response913- expectedResponse string // expected substring in first line of http response
913-}914-}
914-915-
916-func expectTest(contentLength int, expectation string, readBody bool, expectedResponse string) serverExpectTest {
917- return serverExpectTest{
918- contentLength: contentLength,
919- expectation: expectation,
920- readBody: readBody,
921- expectedResponse: expectedResponse,
922- }
923-}
924-
915-var serverExpectTests = []serverExpectTest{925-var serverExpectTests = []serverExpectTest{
916- // Normal 100-continues, case-insensitive.926- // Normal 100-continues, case-insensitive.
917- {100, "100-continue", true, "100 Continue"},927- expectTest(100, "100-continue", true, "100 Continue"),
918- {100, "100-cOntInUE", true, "100 Continue"},928- expectTest(100, "100-cOntInUE", true, "100 Continue"),
919-929-
920- // No 100-continue.930- // No 100-continue.
921- {100, "", true, "200 OK"},931- expectTest(100, "", true, "200 OK"),
922-932-
923- // 100-continue but requesting client to deny us,933- // 100-continue but requesting client to deny us,
924- // so it never reads the body.934- // so it never reads the body.
925- {100, "100-continue", false, "401 Unauthorized"},935- expectTest(100, "100-continue", false, "401 Unauthorized"),
926- // Likewise without 100-continue:936- // Likewise without 100-continue:
927- {100, "", false, "401 Unauthorized"},937- expectTest(100, "", false, "401 Unauthorized"),
928-938-
929- // Non-standard expectations are failures939- // Non-standard expectations are failures
930- {0, "a-pony", false, "417 Expectation Failed"},940- expectTest(0, "a-pony", false, "417 Expectation Failed"),
931-941-
932- // Expect-100 requested but no body942- // Expect-100 requested but no body (is apparently okay: Issue 7625)
933- {0, "100-continue", true, "400 Bad Request"},943- expectTest(0, "100-continue", true, "200 OK"),
944- // Expect-100 requested but handler doesn't read the body
945- expectTest(0, "100-continue", false, "401 Unauthorized"),
946- // Expect-100 continue with no body, but a chunked body.
947- {
948- expectation: "100-continue",
949- readBody: true,
950- chunked: true,
951- expectedResponse: "100 Continue",
952- },
934-}953-}
935-954-
936-// Tests that the server responds to the "Expect" request header955-// Tests that the server responds to the "Expect" request header
@@ -959,21 +978,38 @@
959-978-
960- // Only send the body immediately if we're acting like an HTTP client979- // Only send the body immediately if we're acting like an HTTP client
961- // that doesn't send 100-continue expectations.980- // that doesn't send 100-continue expectations.
962- writeBody := test.contentLength > 0 && strings.ToLower(test.expectation) != "100-continue"981- writeBody := test.contentLength != 0 && strings.ToLower(test.expectation) != "100-continue"
963-982-
964- go func() {983- go func() {
984- contentLen := fmt.Sprintf("Content-Length: %d", test.contentLength)
985- if test.chunked {
986- contentLen = "Transfer-Encoding: chunked"
987- }
965- _, err := fmt.Fprintf(conn, "POST /?readbody=%v HTTP/1.1\r\n"+988- _, err := fmt.Fprintf(conn, "POST /?readbody=%v HTTP/1.1\r\n"+
966- "Connection: close\r\n"+989- "Connection: close\r\n"+
967- "Content-Length: %d\r\n"+990- "%s\r\n"+
968- "Expect: %s\r\nHost: foo\r\n\r\n",991- "Expect: %s\r\nHost: foo\r\n\r\n",
969- test.readBody, test.contentLength, test.expectation)992- test.readBody, contentLen, test.expectation)
970- if err != nil {993- if err != nil {
971- t.Errorf("On test %#v, error writing request headers: %v", test, err)994- t.Errorf("On test %#v, error writing request headers: %v", test, err)
972- return995- return
973- }996- }
974- if writeBody {997- if writeBody {
998- var targ io.WriteCloser = struct {
999- io.Writer
1000- io.Closer
1001- }{
1002- conn,
1003- ioutil.NopCloser(nil),
1004- }
1005- if test.chunked {
1006- targ = httputil.NewChunkedWriter(conn)
1007- }
975- body := strings.Repeat("A", test.contentLength)1008- body := strings.Repeat("A", test.contentLength)
976- _, err = fmt.Fprint(conn, body)1009- _, err = fmt.Fprint(targ, body)
1010- if err == nil {
1011- err = targ.Close()
1012- }
977- if err != nil {1013- if err != nil {
978- if !test.readBody {1014- if !test.readBody {
979- // Server likely already hung up on us.1015- // Server likely already hung up on us.
@@ -1352,7 +1388,7 @@
1352 type neverEnding byte1388 type neverEnding byte
1353 1389
1354 func (b neverEnding) Read(p []byte) (n int, err error) {1390 func (b neverEnding) Read(p []byte) (n int, err error) {
1355@@ -1384,1344 +56,3 @@1391@@ -1420,1392 +56,3 @@
1356 }1392 }
1357 return len(p), nil1393 return len(p), nil
1358 }1394 }
@@ -2080,7 +2116,7 @@
2080- got := ht.rawResponse(req)2116- got := ht.rawResponse(req)
2081- wantStatus := fmt.Sprintf("%d %s", code, StatusText(code))2117- wantStatus := fmt.Sprintf("%d %s", code, StatusText(code))
2082- if !strings.Contains(got, wantStatus) {2118- if !strings.Contains(got, wantStatus) {
2083- t.Errorf("Code %d: Wanted %q Modified for %q: %s", code, req, got)2119- t.Errorf("Code %d: Wanted %q Modified for %q: %s", code, wantStatus, req, got)
2084- } else if strings.Contains(got, "Content-Length") {2120- } else if strings.Contains(got, "Content-Length") {
2085- t.Errorf("Code %d: Got a Content-Length from %q: %s", code, req, got)2121- t.Errorf("Code %d: Got a Content-Length from %q: %s", code, req, got)
2086- } else if strings.Contains(got, "stuff") {2122- } else if strings.Contains(got, "stuff") {
@@ -2090,6 +2126,21 @@
2090- }2126- }
2091-}2127-}
2092-2128-
2129-func TestContentTypeOkayOn204(t *testing.T) {
2130- ht := newHandlerTest(HandlerFunc(func(w ResponseWriter, r *Request) {
2131- w.Header().Set("Content-Length", "123") // suppressed
2132- w.Header().Set("Content-Type", "foo/bar")
2133- w.WriteHeader(204)
2134- }))
2135- got := ht.rawResponse("GET / HTTP/1.1")
2136- if !strings.Contains(got, "Content-Type: foo/bar") {
2137- t.Errorf("Response = %q; want Content-Type: foo/bar", got)
2138- }
2139- if strings.Contains(got, "Content-Length: 123") {
2140- t.Errorf("Response = %q; don't want a Content-Length", got)
2141- }
2142-}
2143-
2093-// Issue 69952144-// Issue 6995
2094-// A server Handler can receive a Request, and then turn around and2145-// A server Handler can receive a Request, and then turn around and
2095-// give a copy of that Request.Body out to the Transport (e.g. any2146-// give a copy of that Request.Body out to the Transport (e.g. any
@@ -2261,7 +2312,7 @@
2261- ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0)2312- ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0)
2262- ts.Config.ConnState = func(c net.Conn, state ConnState) {2313- ts.Config.ConnState = func(c net.Conn, state ConnState) {
2263- if c == nil {2314- if c == nil {
2264- t.Error("nil conn seen in state %s", state)2315- t.Errorf("nil conn seen in state %s", state)
2265- return2316- return
2266- }2317- }
2267- mu.Lock()2318- mu.Lock()
@@ -2397,6 +2448,39 @@
2397- }2448- }
2398-}2449-}
2399-2450-
2451-// golang.org/issue/7856
2452-func TestServerEmptyBodyRace(t *testing.T) {
2453- defer afterTest(t)
2454- var n int32
2455- ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) {
2456- atomic.AddInt32(&n, 1)
2457- }))
2458- defer ts.Close()
2459- var wg sync.WaitGroup
2460- const reqs = 20
2461- for i := 0; i < reqs; i++ {
2462- wg.Add(1)
2463- go func() {
2464- defer wg.Done()
2465- res, err := Get(ts.URL)
2466- if err != nil {
2467- t.Error(err)
2468- return
2469- }
2470- defer res.Body.Close()
2471- _, err = io.Copy(ioutil.Discard, res.Body)
2472- if err != nil {
2473- t.Error(err)
2474- return
2475- }
2476- }()
2477- }
2478- wg.Wait()
2479- if got := atomic.LoadInt32(&n); got != reqs {
2480- t.Errorf("handler ran %d times; want %d", got, reqs)
2481- }
2482-}
2483-
2400-func TestServerConnStateNew(t *testing.T) {2484-func TestServerConnStateNew(t *testing.T) {
2401- sawNew := false // if the test is buggy, we'll race on this variable.2485- sawNew := false // if the test is buggy, we'll race on this variable.
2402- srv := &Server{2486- srv := &Server{
@@ -2699,9 +2783,9 @@
2699-}2783-}
27002784
2701=== modified file 'http13client/server.go'2785=== modified file 'http13client/server.go'
2702--- http13client/server.go 2014-03-19 20:20:19 +00002786--- http13client/server.go 2014-06-20 11:00:47 +0000
2703+++ http13client/server.go 2014-03-19 22:27:37 +00002787+++ http13client/server.go 2014-06-20 12:05:53 +0000
2704@@ -2,1984 +2,17 @@2788@@ -2,1976 +2,16 @@
2705 // Use of this source code is governed by a BSD-style2789 // Use of this source code is governed by a BSD-style
2706 // license that can be found in the LICENSE file.2790 // license that can be found in the LICENSE file.
2707 2791
@@ -2723,7 +2807,7 @@
2723- "path"2807- "path"
2724- "runtime"2808- "runtime"
2725- "strconv"2809- "strconv"
2726 "strings"2810- "strings"
2727 "sync"2811 "sync"
2728- "sync/atomic"2812- "sync/atomic"
2729- "time"2813- "time"
@@ -3506,18 +3590,16 @@
3506- }3590- }
3507-3591-
3508- code := w.status3592- code := w.status
3509- if !bodyAllowedForStatus(code) {3593- if bodyAllowedForStatus(code) {
3510- // Must not have body.
3511- // RFC 2616 section 10.3.5: "the response MUST NOT include other entity-headers"
3512- for _, k := range []string{"Content-Type", "Content-Length", "Transfer-Encoding"} {
3513- delHeader(k)
3514- }
3515- } else {
3516- // If no content type, apply sniffing algorithm to body.3594- // If no content type, apply sniffing algorithm to body.
3517- _, haveType := header["Content-Type"]3595- _, haveType := header["Content-Type"]
3518- if !haveType {3596- if !haveType {
3519- setHeader.contentType = DetectContentType(p)3597- setHeader.contentType = DetectContentType(p)
3520- }3598- }
3599- } else {
3600- for _, k := range suppressedHeaders(code) {
3601- delHeader(k)
3602- }
3521- }3603- }
3522-3604-
3523- if _, ok := header["Date"]; !ok {3605- if _, ok := header["Date"]; !ok {
@@ -3865,16 +3947,10 @@
3865- // Expect 100 Continue support3947- // Expect 100 Continue support
3866- req := w.req3948- req := w.req
3867- if req.expectsContinue() {3949- if req.expectsContinue() {
3868- if req.ProtoAtLeast(1, 1) {3950- if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
3869- // Wrap the Body reader with one that replies on the connection3951- // Wrap the Body reader with one that replies on the connection
3870- req.Body = &expectContinueReader{readCloser: req.Body, resp: w}3952- req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
3871- }3953- }
3872- if req.ContentLength == 0 {
3873- w.Header().Set("Connection", "close")
3874- w.WriteHeader(StatusBadRequest)
3875- w.finishRequest()
3876- break
3877- }
3878- req.Header.Del("Expect")3954- req.Header.Del("Expect")
3879- } else if req.Header.get("Expect") != "" {3955- } else if req.Header.get("Expect") != "" {
3880- w.sendExpectationFailed()3956- w.sendExpectationFailed()
@@ -4685,11 +4761,11 @@
4685-}4761-}
4686+)4762+)
4687 4763
4688 // eofReader is a non-nil io.ReadCloser that always returns EOF.4764 type eofReaderWithWriteTo struct{}
4689 // It embeds a *strings.Reader so it still has a WriteTo method4765
4690@@ -1992,28 +25,6 @@4766@@ -1991,28 +31,6 @@
4691 ioutil.NopCloser(nil),4767 // Verify that an io.Copy from an eofReader won't require a buffer.
4692 }4768 var _ io.WriterTo = eofReader
4693 4769
4694-// initNPNRequest is an HTTP handler that initializes certain4770-// initNPNRequest is an HTTP handler that initializes certain
4695-// uninitialized fields in its *Request. Such partially-initialized4771-// uninitialized fields in its *Request. Such partially-initialized
46964772
=== modified file 'http13client/_patches/fix_code.patch'
--- http13client/_patches/fix_code.patch 2014-03-19 23:13:58 +0000
+++ http13client/_patches/fix_code.patch 2014-06-20 13:02:58 +0000
@@ -1,6 +1,6 @@
1=== modified file 'http13client/client.go'1=== modified file 'http13client/client.go'
2--- http13client/client.go 2014-03-19 20:20:19 +00002--- http13client/client.go 2014-06-20 11:00:47 +0000
3+++ http13client/client.go 2014-03-19 22:27:37 +00003+++ http13client/client.go 2014-06-20 12:05:53 +0000
4@@ -17,6 +17,7 @@4@@ -17,6 +17,7 @@
5 "io/ioutil"5 "io/ioutil"
6 "log"6 "log"
@@ -18,7 +18,7 @@
18 18
19 // Timeout specifies a time limit for requests made by this19 // Timeout specifies a time limit for requests made by this
20 // Client. The timeout includes connection time, any20 // Client. The timeout includes connection time, any
21@@ -177,7 +178,7 @@21@@ -184,7 +185,7 @@
22 // Headers, leaving it uninitialized. We guarantee to the22 // Headers, leaving it uninitialized. We guarantee to the
23 // Transport that this has been initialized, though.23 // Transport that this has been initialized, though.
24 if req.Header == nil {24 if req.Header == nil {
@@ -27,7 +27,7 @@
27 }27 }
28 28
29 if u := req.URL.User; u != nil {29 if u := req.URL.User; u != nil {
30@@ -308,7 +309,7 @@30@@ -316,7 +317,7 @@
31 if ireq.Method == "POST" || ireq.Method == "PUT" {31 if ireq.Method == "POST" || ireq.Method == "PUT" {
32 nreq.Method = "GET"32 nreq.Method = "GET"
33 }33 }
@@ -38,8 +38,8 @@
38 break38 break
3939
40=== modified file 'http13client/cookie.go'40=== modified file 'http13client/cookie.go'
41--- http13client/cookie.go 2014-03-19 20:20:19 +000041--- http13client/cookie.go 2014-06-20 11:00:47 +0000
42+++ http13client/cookie.go 2014-03-19 22:27:37 +000042+++ http13client/cookie.go 2014-06-20 12:05:53 +0000
43@@ -5,10 +5,9 @@43@@ -5,10 +5,9 @@
44 package http44 package http
45 45
@@ -94,7 +94,7 @@
94 Name: name,94 Name: name,
95 Value: value,95 Value: value,
96 Raw: line,96 Raw: line,
97@@ -129,59 +108,12 @@97@@ -125,59 +104,12 @@
98 return cookies98 return cookies
99 }99 }
100 100
@@ -156,7 +156,7 @@
156 lines, ok := h["Cookie"]156 lines, ok := h["Cookie"]
157 if !ok {157 if !ok {
158 return cookies158 return cookies
159@@ -213,7 +145,7 @@159@@ -209,7 +141,7 @@
160 if !success {160 if !success {
161 continue161 continue
162 }162 }
@@ -167,8 +167,8 @@
167 }167 }
168168
169=== modified file 'http13client/header.go'169=== modified file 'http13client/header.go'
170--- http13client/header.go 2014-03-19 20:20:19 +0000170--- http13client/header.go 2014-06-20 11:00:47 +0000
171+++ http13client/header.go 2014-03-19 22:27:37 +0000171+++ http13client/header.go 2014-06-20 12:00:22 +0000
172@@ -5,176 +5,9 @@172@@ -5,176 +5,9 @@
173 package http173 package http
174 174
@@ -348,8 +348,8 @@
348 // token must be all lowercase.348 // token must be all lowercase.
349349
350=== modified file 'http13client/request.go'350=== modified file 'http13client/request.go'
351--- http13client/request.go 2014-03-19 20:20:19 +0000351--- http13client/request.go 2014-06-20 11:00:47 +0000
352+++ http13client/request.go 2014-03-19 22:27:37 +0000352+++ http13client/request.go 2014-06-20 12:05:53 +0000
353@@ -16,6 +16,7 @@353@@ -16,6 +16,7 @@
354 "io/ioutil"354 "io/ioutil"
355 "mime"355 "mime"
@@ -358,25 +358,25 @@
358 "net/textproto"358 "net/textproto"
359 "net/url"359 "net/url"
360 "strconv"360 "strconv"
361@@ -103,7 +104,7 @@361@@ -121,7 +122,7 @@
362 // The request parser implements this by canonicalizing the362 // added and may override values in Header.
363 // name, making the first character and any characters363 //
364 // following a hyphen uppercase and the rest lowercase.364 // See the documentation for the Request.Write method.
365- Header Header365- Header Header
366+ Header http.Header366+ Header http.Header
367 367
368 // Body is the request's body.368 // Body is the request's body.
369 //369 //
370@@ -164,7 +165,7 @@370@@ -199,7 +200,7 @@
371 // For server requests, Trailer is only populated after Body has been371 // not mutate Trailer.
372 // closed or fully consumed.372 //
373 // Trailer support is only partially complete.373 // Few HTTP clients, servers, or proxies support HTTP trailers.
374- Trailer Header374- Trailer Header
375+ Trailer http.Header375+ Trailer http.Header
376 376
377 // RemoteAddr allows HTTP servers and other software to record377 // RemoteAddr allows HTTP servers and other software to record
378 // the network address that sent the request, usually for378 // the network address that sent the request, usually for
379@@ -204,7 +205,7 @@379@@ -239,7 +240,7 @@
380 }380 }
381 381
382 // Cookies parses and returns the HTTP cookies sent with the request.382 // Cookies parses and returns the HTTP cookies sent with the request.
@@ -385,7 +385,7 @@
385 return readCookies(r.Header, "")385 return readCookies(r.Header, "")
386 }386 }
387 387
388@@ -212,7 +213,7 @@388@@ -247,7 +248,7 @@
389 389
390 // Cookie returns the named cookie provided in the request or390 // Cookie returns the named cookie provided in the request or
391 // ErrNoCookie if not found.391 // ErrNoCookie if not found.
@@ -394,7 +394,7 @@
394 for _, c := range readCookies(r.Header, name) {394 for _, c := range readCookies(r.Header, name) {
395 return c, nil395 return c, nil
396 }396 }
397@@ -223,7 +224,7 @@397@@ -258,7 +259,7 @@
398 // AddCookie does not attach more than one Cookie header field. That398 // AddCookie does not attach more than one Cookie header field. That
399 // means all cookies, if any, are written into the same line,399 // means all cookies, if any, are written into the same line,
400 // separated by semicolon.400 // separated by semicolon.
@@ -403,7 +403,7 @@
403 s := fmt.Sprintf("%s=%s", sanitizeCookieName(c.Name), sanitizeCookieValue(c.Value))403 s := fmt.Sprintf("%s=%s", sanitizeCookieName(c.Name), sanitizeCookieValue(c.Value))
404 if c := r.Header.Get("Cookie"); c != "" {404 if c := r.Header.Get("Cookie"); c != "" {
405 r.Header.Set("Cookie", c+"; "+s)405 r.Header.Set("Cookie", c+"; "+s)
406@@ -326,7 +327,7 @@406@@ -361,7 +362,7 @@
407 }407 }
408 408
409 // extraHeaders may be nil409 // extraHeaders may be nil
@@ -412,7 +412,7 @@
412 host := req.Host412 host := req.Host
413 if host == "" {413 if host == "" {
414 if req.URL == nil {414 if req.URL == nil {
415@@ -456,7 +457,7 @@415@@ -490,7 +491,7 @@
416 Proto: "HTTP/1.1",416 Proto: "HTTP/1.1",
417 ProtoMajor: 1,417 ProtoMajor: 1,
418 ProtoMinor: 1,418 ProtoMinor: 1,
@@ -421,7 +421,7 @@
421 Body: rc,421 Body: rc,
422 Host: u.Host,422 Host: u.Host,
423 }423 }
424@@ -571,7 +572,7 @@424@@ -605,7 +606,7 @@
425 if err != nil {425 if err != nil {
426 return nil, err426 return nil, err
427 }427 }
@@ -430,7 +430,7 @@
430 430
431 // RFC2616: Must treat431 // RFC2616: Must treat
432 // GET /index.html HTTP/1.1432 // GET /index.html HTTP/1.1
433@@ -582,7 +583,7 @@433@@ -616,7 +617,7 @@
434 // the same. In the second case, any Host line is ignored.434 // the same. In the second case, any Host line is ignored.
435 req.Host = req.URL.Host435 req.Host = req.URL.Host
436 if req.Host == "" {436 if req.Host == "" {
@@ -439,7 +439,7 @@
439 }439 }
440 delete(req.Header, "Host")440 delete(req.Header, "Host")
441 441
442@@ -630,12 +631,12 @@442@@ -638,12 +639,12 @@
443 //443 //
444 // MaxBytesReader prevents clients from accidentally or maliciously444 // MaxBytesReader prevents clients from accidentally or maliciously
445 // sending a large request and wasting server resources.445 // sending a large request and wasting server resources.
@@ -454,7 +454,7 @@
454 r io.ReadCloser // underlying reader454 r io.ReadCloser // underlying reader
455 n int64 // max bytes remaining455 n int64 // max bytes remaining
456 stopped bool456 stopped bool
457@@ -645,9 +646,6 @@457@@ -653,9 +654,6 @@
458 if l.n <= 0 {458 if l.n <= 0 {
459 if !l.stopped {459 if !l.stopped {
460 l.stopped = true460 l.stopped = true
@@ -464,7 +464,7 @@
464 }464 }
465 return 0, errors.New("http: request body too large")465 return 0, errors.New("http: request body too large")
466 }466 }
467@@ -852,16 +850,16 @@467@@ -858,18 +856,18 @@
468 }468 }
469 469
470 func (r *Request) expectsContinue() bool {470 func (r *Request) expectsContinue() bool {
@@ -484,11 +484,13 @@
484- return hasToken(r.Header.get("Connection"), "close")484- return hasToken(r.Header.get("Connection"), "close")
485+ return hasToken(r.Header.Get("Connection"), "close")485+ return hasToken(r.Header.Get("Connection"), "close")
486 }486 }
487
488 func (r *Request) closeBody() {
487489
488=== modified file 'http13client/response.go'490=== modified file 'http13client/response.go'
489--- http13client/response.go 2014-03-19 20:20:19 +0000491--- http13client/response.go 2014-06-20 11:00:47 +0000
490+++ http13client/response.go 2014-03-19 22:27:37 +0000492+++ http13client/response.go 2014-06-20 12:05:53 +0000
491@@ -11,6 +11,7 @@493@@ -12,6 +12,7 @@
492 "crypto/tls"494 "crypto/tls"
493 "errors"495 "errors"
494 "io"496 "io"
@@ -496,7 +498,7 @@
496 "net/textproto"498 "net/textproto"
497 "net/url"499 "net/url"
498 "strconv"500 "strconv"
499@@ -40,7 +41,7 @@501@@ -41,7 +42,7 @@
500 // omitted from Header.502 // omitted from Header.
501 //503 //
502 // Keys in the map are canonicalized (see CanonicalHeaderKey).504 // Keys in the map are canonicalized (see CanonicalHeaderKey).
@@ -505,7 +507,7 @@
505 507
506 // Body represents the response body.508 // Body represents the response body.
507 //509 //
508@@ -69,7 +70,7 @@510@@ -71,7 +72,7 @@
509 511
510 // Trailer maps trailer keys to values, in the same512 // Trailer maps trailer keys to values, in the same
511 // format as the header.513 // format as the header.
@@ -514,7 +516,7 @@
514 516
515 // The Request that was sent to obtain this Response.517 // The Request that was sent to obtain this Response.
516 // Request's Body is nil (having already been consumed).518 // Request's Body is nil (having already been consumed).
517@@ -84,7 +85,7 @@519@@ -86,7 +87,7 @@
518 }520 }
519 521
520 // Cookies parses and returns the cookies set in the Set-Cookie headers.522 // Cookies parses and returns the cookies set in the Set-Cookie headers.
@@ -523,7 +525,7 @@
523 return readSetCookies(r.Header)525 return readSetCookies(r.Header)
524 }526 }
525 527
526@@ -153,7 +154,7 @@528@@ -155,7 +156,7 @@
527 }529 }
528 return nil, err530 return nil, err
529 }531 }
@@ -532,7 +534,7 @@
532 534
533 fixPragmaCacheControl(resp.Header)535 fixPragmaCacheControl(resp.Header)
534 536
535@@ -169,7 +170,7 @@537@@ -171,7 +172,7 @@
536 // Pragma: no-cache538 // Pragma: no-cache
537 // like539 // like
538 // Cache-Control: no-cache540 // Cache-Control: no-cache
@@ -543,17 +545,17 @@
543 header["Cache-Control"] = []string{"no-cache"}545 header["Cache-Control"] = []string{"no-cache"}
544546
545=== modified file 'http13client/transfer.go'547=== modified file 'http13client/transfer.go'
546--- http13client/transfer.go 2014-03-19 20:20:19 +0000548--- http13client/transfer.go 2014-06-20 11:00:47 +0000
547+++ http13client/transfer.go 2014-03-19 22:27:37 +0000549+++ http13client/transfer.go 2014-06-20 12:05:53 +0000
548@@ -11,6 +11,7 @@550@@ -11,6 +11,7 @@
549 "fmt"551 "fmt"
550 "io"552 "io"
551 "io/ioutil"553 "io/ioutil"
552+ "net/http"554+ "net/http"
553 "net/textproto"555 "net/textproto"
556 "sort"
554 "strconv"557 "strconv"
555 "strings"558@@ -37,7 +38,7 @@
556@@ -36,7 +37,7 @@
557 ContentLength int64 // -1 means unknown, 0 means exactly none559 ContentLength int64 // -1 means unknown, 0 means exactly none
558 Close bool560 Close bool
559 TransferEncoding []string561 TransferEncoding []string
@@ -562,16 +564,16 @@
562 }564 }
563 565
564 func newTransferWriter(r interface{}) (t *transferWriter, err error) {566 func newTransferWriter(r interface{}) (t *transferWriter, err error) {
565@@ -174,7 +175,7 @@567@@ -171,7 +172,7 @@
566 io.WriteString(w, "Trailer: ")568 if t.Trailer != nil {
567 needComma := false569 keys := make([]string, 0, len(t.Trailer))
568 for k := range t.Trailer {570 for k := range t.Trailer {
569- k = CanonicalHeaderKey(k)571- k = CanonicalHeaderKey(k)
570+ k = http.CanonicalHeaderKey(k)572+ k = http.CanonicalHeaderKey(k)
571 switch k {573 switch k {
572 case "Transfer-Encoding", "Trailer", "Content-Length":574 case "Transfer-Encoding", "Trailer", "Content-Length":
573 return &badStringError{"invalid Trailer key", k}575 return &badStringError{"invalid Trailer key", k}
574@@ -237,7 +238,7 @@576@@ -243,7 +244,7 @@
575 577
576 type transferReader struct {578 type transferReader struct {
577 // Input579 // Input
@@ -580,7 +582,7 @@
580 StatusCode int582 StatusCode int
581 RequestMethod string583 RequestMethod string
582 ProtoMajor int584 ProtoMajor int
583@@ -247,7 +248,7 @@585@@ -253,7 +254,7 @@
584 ContentLength int64586 ContentLength int64
585 TransferEncoding []string587 TransferEncoding []string
586 Close bool588 Close bool
@@ -589,7 +591,7 @@
589 }591 }
590 592
591 // bodyAllowedForStatus reports whether a given response status code593 // bodyAllowedForStatus reports whether a given response status code
592@@ -308,7 +309,7 @@594@@ -330,7 +331,7 @@
593 return err595 return err
594 }596 }
595 if isResponse && t.RequestMethod == "HEAD" {597 if isResponse && t.RequestMethod == "HEAD" {
@@ -598,7 +600,7 @@
598 return err600 return err
599 } else {601 } else {
600 t.ContentLength = n602 t.ContentLength = n
601@@ -386,7 +387,7 @@603@@ -408,7 +409,7 @@
602 func isIdentity(te []string) bool { return len(te) == 1 && te[0] == "identity" }604 func isIdentity(te []string) bool { return len(te) == 1 && te[0] == "identity" }
603 605
604 // Sanitize transfer encoding606 // Sanitize transfer encoding
@@ -607,7 +609,7 @@
607 raw, present := header["Transfer-Encoding"]609 raw, present := header["Transfer-Encoding"]
608 if !present {610 if !present {
609 return nil, nil611 return nil, nil
610@@ -429,7 +430,7 @@612@@ -451,7 +452,7 @@
611 // Determine the expected body length, using RFC 2616 Section 4.4. This613 // Determine the expected body length, using RFC 2616 Section 4.4. This
612 // function is not a method, because ultimately it should be shared by614 // function is not a method, because ultimately it should be shared by
613 // ReadResponse and ReadRequest.615 // ReadResponse and ReadRequest.
@@ -616,7 +618,7 @@
616 618
617 // Logic based on response type or status619 // Logic based on response type or status
618 if noBodyExpected(requestMethod) {620 if noBodyExpected(requestMethod) {
619@@ -449,7 +450,7 @@621@@ -471,7 +472,7 @@
620 }622 }
621 623
622 // Logic based on Content-Length624 // Logic based on Content-Length
@@ -625,7 +627,7 @@
625 if cl != "" {627 if cl != "" {
626 n, err := parseContentLength(cl)628 n, err := parseContentLength(cl)
627 if err != nil {629 if err != nil {
628@@ -475,18 +476,18 @@630@@ -497,18 +498,18 @@
629 // Determine whether to hang up after sending a request and body, or631 // Determine whether to hang up after sending a request and body, or
630 // receiving a response and body632 // receiving a response and body
631 // 'header' is the request headers633 // 'header' is the request headers
@@ -647,7 +649,7 @@
647 header.Del("Connection")649 header.Del("Connection")
648 return true650 return true
649 }651 }
650@@ -495,17 +496,17 @@652@@ -517,17 +518,17 @@
651 }653 }
652 654
653 // Parse the trailer header655 // Parse the trailer header
@@ -669,22 +671,28 @@
669 switch key {671 switch key {
670 case "Transfer-Encoding", "Trailer", "Content-Length":672 case "Transfer-Encoding", "Trailer", "Content-Length":
671 return nil, &badStringError{"bad trailer key", key}673 return nil, &badStringError{"bad trailer key", key}
672@@ -642,9 +643,9 @@674@@ -664,14 +665,14 @@
673 }675 }
674 switch rr := b.hdr.(type) {676 switch rr := b.hdr.(type) {
675 case *Request:677 case *Request:
676- rr.Trailer = Header(hdr)678- mergeSetHeader(&rr.Trailer, Header(hdr))
677+ rr.Trailer = http.Header(hdr)679+ mergeSetHeader(&rr.Trailer, http.Header(hdr))
678 case *Response:680 case *Response:
679- rr.Trailer = Header(hdr)681- mergeSetHeader(&rr.Trailer, Header(hdr))
680+ rr.Trailer = http.Header(hdr)682+ mergeSetHeader(&rr.Trailer, http.Header(hdr))
681 }683 }
682 return nil684 return nil
683 }685 }
686
687-func mergeSetHeader(dst *Header, src Header) {
688+func mergeSetHeader(dst *http.Header, src http.Header) {
689 if *dst == nil {
690 *dst = src
691 return
684692
685=== modified file 'http13client/transport.go'693=== modified file 'http13client/transport.go'
686--- http13client/transport.go 2014-03-19 20:20:19 +0000694--- http13client/transport.go 2014-06-20 11:00:47 +0000
687+++ http13client/transport.go 2014-03-19 22:27:37 +0000695+++ http13client/transport.go 2014-06-20 12:05:53 +0000
688@@ -18,6 +18,7 @@696@@ -18,6 +18,7 @@
689 "io"697 "io"
690 "log"698 "log"
@@ -693,7 +701,7 @@
693 "net/url"701 "net/url"
694 "os"702 "os"
695 "strings"703 "strings"
696@@ -144,12 +145,12 @@704@@ -147,12 +148,12 @@
697 // optional extra headers to write.705 // optional extra headers to write.
698 type transportRequest struct {706 type transportRequest struct {
699 *Request // original request, not to be mutated707 *Request // original request, not to be mutated
@@ -709,7 +717,7 @@
709 }717 }
710 return tr.extra718 return tr.extra
711 }719 }
712@@ -512,7 +513,7 @@720@@ -519,7 +520,7 @@
713 case cm.targetScheme == "http":721 case cm.targetScheme == "http":
714 pconn.isProxy = true722 pconn.isProxy = true
715 if pa != "" {723 if pa != "" {
@@ -718,7 +726,7 @@
718 h.Set("Proxy-Authorization", pa)726 h.Set("Proxy-Authorization", pa)
719 }727 }
720 }728 }
721@@ -521,7 +522,7 @@729@@ -528,7 +529,7 @@
722 Method: "CONNECT",730 Method: "CONNECT",
723 URL: &url.URL{Opaque: cm.targetAddr},731 URL: &url.URL{Opaque: cm.targetAddr},
724 Host: cm.targetAddr,732 Host: cm.targetAddr,
@@ -727,7 +735,7 @@
727 }735 }
728 if pa != "" {736 if pa != "" {
729 connectReq.Header.Set("Proxy-Authorization", pa)737 connectReq.Header.Set("Proxy-Authorization", pa)
730@@ -735,7 +736,7 @@738@@ -748,7 +749,7 @@
731 // mutateHeaderFunc is an optional func to modify extra739 // mutateHeaderFunc is an optional func to modify extra
732 // headers on each outbound request before it's written. (the740 // headers on each outbound request before it's written. (the
733 // original Request given to RoundTrip is not modified)741 // original Request given to RoundTrip is not modified)
@@ -735,5 +743,5 @@
735+ mutateHeaderFunc func(http.Header)743+ mutateHeaderFunc func(http.Header)
736 }744 }
737 745
738 func (pc *persistConn) isBroken() bool {746 // isBroken reports whether this connection is in a known broken state.
739747
740748
=== modified file 'http13client/_patches/fix_status.patch'
--- http13client/_patches/fix_status.patch 2014-03-19 23:43:25 +0000
+++ http13client/_patches/fix_status.patch 2014-06-20 13:02:58 +0000
@@ -1,7 +1,7 @@
1=== modified file 'http13client/client.go'1=== modified file 'http13client/client.go'
2--- http13client/client.go 2014-03-19 23:13:58 +00002--- http13client/client.go 2014-06-20 12:46:25 +0000
3+++ http13client/client.go 2014-03-19 23:38:11 +00003+++ http13client/client.go 2014-06-20 12:46:45 +0000
4@@ -210,7 +210,7 @@4@@ -217,7 +217,7 @@
5 // automatically redirect.5 // automatically redirect.
6 func shouldRedirectGet(statusCode int) bool {6 func shouldRedirectGet(statusCode int) bool {
7 switch statusCode {7 switch statusCode {
@@ -10,7 +10,7 @@
10 return true10 return true
11 }11 }
12 return false12 return false
13@@ -220,7 +220,7 @@13@@ -227,7 +227,7 @@
14 // automatically redirect.14 // automatically redirect.
15 func shouldRedirectPost(statusCode int) bool {15 func shouldRedirectPost(statusCode int) bool {
16 switch statusCode {16 switch statusCode {
@@ -21,9 +21,9 @@
21 return false21 return false
2222
23=== modified file 'http13client/client_test.go'23=== modified file 'http13client/client_test.go'
24--- http13client/client_test.go 2014-03-19 23:13:58 +000024--- http13client/client_test.go 2014-06-20 12:46:25 +0000
25+++ http13client/client_test.go 2014-03-19 23:39:48 +000025+++ http13client/client_test.go 2014-06-20 12:46:45 +0000
26@@ -202,7 +202,7 @@26@@ -204,7 +204,7 @@
27 }27 }
28 }28 }
29 if n < 15 {29 if n < 15 {
@@ -32,7 +32,7 @@
32 return32 return
33 }33 }
34 fmt.Fprintf(w, "n=%d", n)34 fmt.Fprintf(w, "n=%d", n)
35@@ -324,7 +324,7 @@35@@ -326,7 +326,7 @@
36 }36 }
37 if r.URL.Path == "/" {37 if r.URL.Path == "/" {
38 http.SetCookie(w, expectedCookies[1])38 http.SetCookie(w, expectedCookies[1])
@@ -41,7 +41,7 @@
41 } else {41 } else {
42 http.SetCookie(w, expectedCookies[2])42 http.SetCookie(w, expectedCookies[2])
43 w.Write([]byte("hello"))43 w.Write([]byte("hello"))
44@@ -783,7 +783,7 @@44@@ -785,7 +785,7 @@
45 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {45 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
46 if r.URL.Path == "/" {46 if r.URL.Path == "/" {
47 sawRoot <- true47 sawRoot <- true
@@ -50,7 +50,7 @@
50 return50 return
51 }51 }
52 if r.URL.Path == "/slow" {52 if r.URL.Path == "/slow" {
53@@ -844,7 +844,7 @@53@@ -846,7 +846,7 @@
54 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {54 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
55 saw <- r.RemoteAddr55 saw <- r.RemoteAddr
56 if r.URL.Path == "/" {56 if r.URL.Path == "/" {
@@ -61,9 +61,9 @@
61 defer ts.Close()61 defer ts.Close()
6262
63=== modified file 'http13client/request_test.go'63=== modified file 'http13client/request_test.go'
64--- http13client/request_test.go 2014-03-19 23:13:58 +000064--- http13client/request_test.go 2014-06-20 12:46:25 +0000
65+++ http13client/request_test.go 2014-03-19 23:40:12 +000065+++ http13client/request_test.go 2014-06-20 12:46:45 +0000
66@@ -164,11 +164,11 @@66@@ -182,11 +182,11 @@
67 switch r.URL.Path {67 switch r.URL.Path {
68 case "/":68 case "/":
69 w.Header().Set("Location", "/foo/")69 w.Header().Set("Location", "/foo/")
@@ -79,9 +79,9 @@
79 defer ts.Close()79 defer ts.Close()
8080
81=== modified file 'http13client/response.go'81=== modified file 'http13client/response.go'
82--- http13client/response.go 2014-03-19 23:13:58 +000082--- http13client/response.go 2014-06-20 12:46:25 +0000
83+++ http13client/response.go 2014-03-19 23:38:56 +000083+++ http13client/response.go 2014-06-20 12:46:45 +0000
84@@ -204,9 +204,8 @@84@@ -205,9 +205,8 @@
85 // Status line85 // Status line
86 text := r.Status86 text := r.Status
87 if text == "" {87 if text == "" {
@@ -94,10 +94,23 @@
94 }94 }
95 }95 }
9696
97=== modified file 'http13client/responsewrite_test.go'
98--- http13client/responsewrite_test.go 2014-06-20 12:46:25 +0000
99+++ http13client/responsewrite_test.go 2014-06-20 12:47:05 +0000
100@@ -197,7 +197,7 @@
101 // there were two.
102 {
103 Response{
104- StatusCode: StatusOK,
105+ StatusCode: http.StatusOK,
106 ProtoMajor: 1,
107 ProtoMinor: 1,
108 Request: &Request{Method: "POST"},
109
97=== modified file 'http13client/transport_test.go'110=== modified file 'http13client/transport_test.go'
98--- http13client/transport_test.go 2014-03-19 23:13:58 +0000111--- http13client/transport_test.go 2014-06-20 12:46:25 +0000
99+++ http13client/transport_test.go 2014-03-19 23:40:39 +0000112+++ http13client/transport_test.go 2014-06-20 12:46:45 +0000
100@@ -968,7 +968,7 @@113@@ -1004,7 +1004,7 @@
101 defer afterTest(t)114 defer afterTest(t)
102 const deniedMsg = "sorry, denied."115 const deniedMsg = "sorry, denied."
103 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {116 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -106,7 +119,7 @@
106 }))119 }))
107 defer ts.Close()120 defer ts.Close()
108 tr := &Transport{}121 tr := &Transport{}
109@@ -992,7 +992,7 @@122@@ -1028,7 +1028,7 @@
110 func TestChunkedNoContent(t *testing.T) {123 func TestChunkedNoContent(t *testing.T) {
111 defer afterTest(t)124 defer afterTest(t)
112 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {125 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
113126
=== modified file 'http13client/_patches/fix_tests.patch'
--- http13client/_patches/fix_tests.patch 2014-03-19 23:13:58 +0000
+++ http13client/_patches/fix_tests.patch 2014-06-20 13:02:58 +0000
@@ -1,6 +1,6 @@
1=== modified file 'http13client/client_test.go'1=== modified file 'http13client/client_test.go'
2--- http13client/client_test.go 2014-03-19 21:38:56 +00002--- http13client/client_test.go 2014-06-20 11:00:47 +0000
3+++ http13client/client_test.go 2014-03-19 22:27:37 +00003+++ http13client/client_test.go 2014-06-20 12:05:53 +0000
4@@ -15,8 +15,8 @@4@@ -15,8 +15,8 @@
5 "fmt"5 "fmt"
6 "io"6 "io"
@@ -11,7 +11,7 @@
11 . "launchpad.net/ubuntu-push/http13client"11 . "launchpad.net/ubuntu-push/http13client"
12 "net/http/httptest"12 "net/http/httptest"
13 "net/url"13 "net/url"
14@@ -27,7 +27,7 @@14@@ -29,7 +29,7 @@
15 "time"15 "time"
16 )16 )
17 17
@@ -20,7 +20,7 @@
20 w.Header().Set("Last-Modified", "sometime")20 w.Header().Set("Last-Modified", "sometime")
21 fmt.Fprintf(w, "User-agent: go\nDisallow: /something/")21 fmt.Fprintf(w, "User-agent: go\nDisallow: /something/")
22 })22 })
23@@ -193,7 +193,7 @@23@@ -195,7 +195,7 @@
24 func TestClientRedirects(t *testing.T) {24 func TestClientRedirects(t *testing.T) {
25 defer afterTest(t)25 defer afterTest(t)
26 var ts *httptest.Server26 var ts *httptest.Server
@@ -29,7 +29,7 @@
29 n, _ := strconv.Atoi(r.FormValue("n"))29 n, _ := strconv.Atoi(r.FormValue("n"))
30 // Test Referer header. (7 is arbitrary position to test at)30 // Test Referer header. (7 is arbitrary position to test at)
31 if n == 7 {31 if n == 7 {
32@@ -202,7 +202,7 @@32@@ -204,7 +204,7 @@
33 }33 }
34 }34 }
35 if n < 15 {35 if n < 15 {
@@ -38,7 +38,7 @@
38 return38 return
39 }39 }
40 fmt.Fprintf(w, "n=%d", n)40 fmt.Fprintf(w, "n=%d", n)
41@@ -271,7 +271,7 @@41@@ -273,7 +273,7 @@
42 bytes.Buffer42 bytes.Buffer
43 }43 }
44 var ts *httptest.Server44 var ts *httptest.Server
@@ -47,7 +47,7 @@
47 log.Lock()47 log.Lock()
48 fmt.Fprintf(&log.Buffer, "%s %s ", r.Method, r.RequestURI)48 fmt.Fprintf(&log.Buffer, "%s %s ", r.Method, r.RequestURI)
49 log.Unlock()49 log.Unlock()
50@@ -312,21 +312,21 @@50@@ -314,21 +314,21 @@
51 }51 }
52 }52 }
53 53
@@ -75,7 +75,7 @@
75 w.Write([]byte("hello"))75 w.Write([]byte("hello"))
76 }76 }
77 })77 })
78@@ -334,7 +334,7 @@78@@ -336,7 +336,7 @@
79 func TestClientSendsCookieFromJar(t *testing.T) {79 func TestClientSendsCookieFromJar(t *testing.T) {
80 tr := &recordingTransport{}80 tr := &recordingTransport{}
81 client := &Client{Transport: tr}81 client := &Client{Transport: tr}
@@ -84,7 +84,7 @@
84 us := "http://dummy.faketld/"84 us := "http://dummy.faketld/"
85 u, _ := url.Parse(us)85 u, _ := url.Parse(us)
86 client.Jar.SetCookies(u, expectedCookies)86 client.Jar.SetCookies(u, expectedCookies)
87@@ -364,19 +364,19 @@87@@ -366,19 +366,19 @@
88 // scope of all cookies.88 // scope of all cookies.
89 type TestJar struct {89 type TestJar struct {
90 m sync.Mutex90 m sync.Mutex
@@ -108,7 +108,7 @@
108 j.m.Lock()108 j.m.Lock()
109 defer j.m.Unlock()109 defer j.m.Unlock()
110 return j.perURL[u.Host]110 return j.perURL[u.Host]
111@@ -391,7 +391,7 @@111@@ -393,7 +393,7 @@
112 Jar: new(TestJar),112 Jar: new(TestJar),
113 }113 }
114 u, _ := url.Parse(ts.URL)114 u, _ := url.Parse(ts.URL)
@@ -117,7 +117,7 @@
117 resp, err := c.Get(ts.URL)117 resp, err := c.Get(ts.URL)
118 if err != nil {118 if err != nil {
119 t.Fatalf("Get: %v", err)119 t.Fatalf("Get: %v", err)
120@@ -400,7 +400,7 @@120@@ -402,7 +402,7 @@
121 matchReturnedCookies(t, expectedCookies, resp.Cookies())121 matchReturnedCookies(t, expectedCookies, resp.Cookies())
122 }122 }
123 123
@@ -126,7 +126,7 @@
126 if len(given) != len(expected) {126 if len(given) != len(expected) {
127 t.Logf("Received cookies: %v", given)127 t.Logf("Received cookies: %v", given)
128 t.Errorf("Expected %d cookies, got %d", len(expected), len(given))128 t.Errorf("Expected %d cookies, got %d", len(expected), len(given))
129@@ -421,14 +421,14 @@129@@ -423,14 +423,14 @@
130 130
131 func TestJarCalls(t *testing.T) {131 func TestJarCalls(t *testing.T) {
132 defer afterTest(t)132 defer afterTest(t)
@@ -144,7 +144,7 @@
144 }144 }
145 }))145 }))
146 defer ts.Close()146 defer ts.Close()
147@@ -468,11 +468,11 @@147@@ -470,11 +470,11 @@
148 log bytes.Buffer148 log bytes.Buffer
149 }149 }
150 150
@@ -158,7 +158,7 @@
158 j.logf("Cookies(%q)\n", u)158 j.logf("Cookies(%q)\n", u)
159 return nil159 return nil
160 }160 }
161@@ -486,11 +486,11 @@161@@ -488,11 +488,11 @@
162 func TestStreamingGet(t *testing.T) {162 func TestStreamingGet(t *testing.T) {
163 defer afterTest(t)163 defer afterTest(t)
164 say := make(chan string)164 say := make(chan string)
@@ -173,7 +173,7 @@
173 }173 }
174 }))174 }))
175 defer ts.Close()175 defer ts.Close()
176@@ -536,7 +536,7 @@176@@ -538,7 +538,7 @@
177 // don't send a TCP packet per line of the http request + body.177 // don't send a TCP packet per line of the http request + body.
178 func TestClientWrites(t *testing.T) {178 func TestClientWrites(t *testing.T) {
179 defer afterTest(t)179 defer afterTest(t)
@@ -182,7 +182,7 @@
182 }))182 }))
183 defer ts.Close()183 defer ts.Close()
184 184
185@@ -568,46 +568,6 @@185@@ -570,46 +570,6 @@
186 }186 }
187 }187 }
188 188
@@ -229,7 +229,7 @@
229 func TestClientErrorWithRequestURI(t *testing.T) {229 func TestClientErrorWithRequestURI(t *testing.T) {
230 defer afterTest(t)230 defer afterTest(t)
231 req, _ := NewRequest("GET", "http://localhost:1234/", nil)231 req, _ := NewRequest("GET", "http://localhost:1234/", nil)
232@@ -639,7 +599,7 @@232@@ -641,7 +601,7 @@
233 233
234 func TestClientWithCorrectTLSServerName(t *testing.T) {234 func TestClientWithCorrectTLSServerName(t *testing.T) {
235 defer afterTest(t)235 defer afterTest(t)
@@ -238,7 +238,7 @@
238 if r.TLS.ServerName != "127.0.0.1" {238 if r.TLS.ServerName != "127.0.0.1" {
239 t.Errorf("expected client to set ServerName 127.0.0.1, got: %q", r.TLS.ServerName)239 t.Errorf("expected client to set ServerName 127.0.0.1, got: %q", r.TLS.ServerName)
240 }240 }
241@@ -652,33 +612,6 @@241@@ -654,33 +614,6 @@
242 }242 }
243 }243 }
244 244
@@ -272,7 +272,7 @@
272 // Test for golang.org/issue/5829; the Transport should respect TLSClientConfig.ServerName272 // Test for golang.org/issue/5829; the Transport should respect TLSClientConfig.ServerName
273 // when not empty.273 // when not empty.
274 //274 //
275@@ -690,7 +623,7 @@275@@ -692,7 +625,7 @@
276 // The httptest.Server has a cert with "example.com" as its name.276 // The httptest.Server has a cert with "example.com" as its name.
277 func TestTransportUsesTLSConfigServerName(t *testing.T) {277 func TestTransportUsesTLSConfigServerName(t *testing.T) {
278 defer afterTest(t)278 defer afterTest(t)
@@ -281,7 +281,7 @@
281 w.Write([]byte("Hello"))281 w.Write([]byte("Hello"))
282 }))282 }))
283 defer ts.Close()283 defer ts.Close()
284@@ -711,7 +644,7 @@284@@ -713,7 +646,7 @@
285 285
286 func TestResponseSetsTLSConnectionState(t *testing.T) {286 func TestResponseSetsTLSConnectionState(t *testing.T) {
287 defer afterTest(t)287 defer afterTest(t)
@@ -290,7 +290,7 @@
290 w.Write([]byte("Hello"))290 w.Write([]byte("Hello"))
291 }))291 }))
292 defer ts.Close()292 defer ts.Close()
293@@ -739,7 +672,7 @@293@@ -741,7 +674,7 @@
294 // Verify Response.ContentLength is populated. http://golang.org/issue/4126294 // Verify Response.ContentLength is populated. http://golang.org/issue/4126
295 func TestClientHeadContentLength(t *testing.T) {295 func TestClientHeadContentLength(t *testing.T) {
296 defer afterTest(t)296 defer afterTest(t)
@@ -299,7 +299,7 @@
299 if v := r.FormValue("cl"); v != "" {299 if v := r.FormValue("cl"); v != "" {
300 w.Header().Set("Content-Length", v)300 w.Header().Set("Content-Length", v)
301 }301 }
302@@ -775,7 +708,7 @@302@@ -777,7 +710,7 @@
303 func TestEmptyPasswordAuth(t *testing.T) {303 func TestEmptyPasswordAuth(t *testing.T) {
304 defer afterTest(t)304 defer afterTest(t)
305 gopher := "gopher"305 gopher := "gopher"
@@ -308,7 +308,7 @@
308 auth := r.Header.Get("Authorization")308 auth := r.Header.Get("Authorization")
309 if strings.HasPrefix(auth, "Basic ") {309 if strings.HasPrefix(auth, "Basic ") {
310 encoded := auth[6:]310 encoded := auth[6:]
311@@ -847,15 +780,15 @@311@@ -849,15 +782,15 @@
312 defer afterTest(t)312 defer afterTest(t)
313 sawRoot := make(chan bool, 1)313 sawRoot := make(chan bool, 1)
314 sawSlow := make(chan bool, 1)314 sawSlow := make(chan bool, 1)
@@ -327,7 +327,7 @@
327 sawSlow <- true327 sawSlow <- true
328 time.Sleep(2 * time.Second)328 time.Sleep(2 * time.Second)
329 return329 return
330@@ -908,10 +841,10 @@330@@ -910,10 +843,10 @@
331 func TestClientRedirectEatsBody(t *testing.T) {331 func TestClientRedirectEatsBody(t *testing.T) {
332 defer afterTest(t)332 defer afterTest(t)
333 saw := make(chan string, 2)333 saw := make(chan string, 2)
@@ -340,233 +340,50 @@
340 }340 }
341 }))341 }))
342 defer ts.Close()342 defer ts.Close()
343343@@ -957,7 +890,7 @@
344=== modified file 'http13client/cookie_test.go'344
345--- http13client/cookie_test.go 2014-03-19 20:20:19 +0000345 func TestClientTrailers(t *testing.T) {
346+++ http13client/cookie_test.go 2014-03-19 22:27:37 +0000346 defer afterTest(t)
347@@ -9,6 +9,7 @@347- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
348 "encoding/json"348+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
349 "fmt"349 w.Header().Set("Connection", "close")
350 "log"350 w.Header().Set("Trailer", "Server-Trailer-A, Server-Trailer-B")
351+ "net/http"351 w.Header().Add("Trailer", "Server-Trailer-C")
352 "os"352@@ -992,9 +925,9 @@
353 "reflect"353 // trailers to be sent, if and only if they were
354 "strings"354 // previously declared with w.Header().Set("Trailer",
355@@ -17,39 +18,39 @@355 // ..keys..)
356 )356- w.(Flusher).Flush()
357 357- conn, buf, _ := w.(Hijacker).Hijack()
358 var writeSetCookiesTests = []struct {358- t := Header{}
359- Cookie *Cookie359+ w.(http.Flusher).Flush()
360+ Cookie *http.Cookie360+ conn, buf, _ := w.(http.Hijacker).Hijack()
361 Raw string361+ t := http.Header{}
362 }{362 t.Set("Server-Trailer-A", "valuea")
363 {363 t.Set("Server-Trailer-C", "valuec") // skipping B
364- &Cookie{Name: "cookie-1", Value: "v$1"},364 buf.WriteString("0\r\n") // eof
365+ &http.Cookie{Name: "cookie-1", Value: "v$1"},365@@ -1015,7 +948,7 @@
366 "cookie-1=v$1",366 req.Trailer["Client-Trailer-B"] = []string{"valueb"}
367 },367 }),
368 {368 ))
369- &Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600},369- req.Trailer = Header{
370+ &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600},370+ req.Trailer = http.Header{
371 "cookie-2=two; Max-Age=3600",371 "Client-Trailer-A": nil, // to be set later
372 },372 "Client-Trailer-B": nil, // to be set later
373 {373 }
374- &Cookie{Name: "cookie-3", Value: "three", Domain: ".example.com"},374@@ -1027,7 +960,7 @@
375+ &http.Cookie{Name: "cookie-3", Value: "three", Domain: ".example.com"},375 if err := wantBody(res, err, "decl: [Client-Trailer-A Client-Trailer-B], vals: valuea, valueb"); err != nil {
376 "cookie-3=three; Domain=example.com",376 t.Error(err)
377 },377 }
378 {378- want := Header{
379- &Cookie{Name: "cookie-4", Value: "four", Path: "/restricted/"},379+ want := http.Header{
380+ &http.Cookie{Name: "cookie-4", Value: "four", Path: "/restricted/"},380 "Server-Trailer-A": []string{"valuea"},
381 "cookie-4=four; Path=/restricted/",381 "Server-Trailer-B": nil,
382 },382 "Server-Trailer-C": []string{"valuec"},
383 {
384- &Cookie{Name: "cookie-5", Value: "five", Domain: "wrong;bad.abc"},
385+ &http.Cookie{Name: "cookie-5", Value: "five", Domain: "wrong;bad.abc"},
386 "cookie-5=five",
387 },
388 {
389- &Cookie{Name: "cookie-6", Value: "six", Domain: "bad-.abc"},
390+ &http.Cookie{Name: "cookie-6", Value: "six", Domain: "bad-.abc"},
391 "cookie-6=six",
392 },
393 {
394- &Cookie{Name: "cookie-7", Value: "seven", Domain: "127.0.0.1"},
395+ &http.Cookie{Name: "cookie-7", Value: "seven", Domain: "127.0.0.1"},
396 "cookie-7=seven; Domain=127.0.0.1",
397 },
398 {
399- &Cookie{Name: "cookie-8", Value: "eight", Domain: "::1"},
400+ &http.Cookie{Name: "cookie-8", Value: "eight", Domain: "::1"},
401 "cookie-8=eight",
402 },
403 }
404@@ -71,10 +72,10 @@
405 }
406 }
407
408-type headerOnlyResponseWriter Header
409+type headerOnlyResponseWriter http.Header
410
411-func (ho headerOnlyResponseWriter) Header() Header {
412- return Header(ho)
413+func (ho headerOnlyResponseWriter) Header() http.Header {
414+ return http.Header(ho)
415 }
416
417 func (ho headerOnlyResponseWriter) Write([]byte) (int, error) {
418@@ -86,9 +87,9 @@
419 }
420
421 func TestSetCookie(t *testing.T) {
422- m := make(Header)
423- SetCookie(headerOnlyResponseWriter(m), &Cookie{Name: "cookie-1", Value: "one", Path: "/restricted/"})
424- SetCookie(headerOnlyResponseWriter(m), &Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600})
425+ m := make(http.Header)
426+ http.SetCookie(headerOnlyResponseWriter(m), &http.Cookie{Name: "cookie-1", Value: "one", Path: "/restricted/"})
427+ http.SetCookie(headerOnlyResponseWriter(m), &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600})
428 if l := len(m["Set-Cookie"]); l != 2 {
429 t.Fatalf("expected %d cookies, got %d", 2, l)
430 }
431@@ -101,19 +102,19 @@
432 }
433
434 var addCookieTests = []struct {
435- Cookies []*Cookie
436+ Cookies []*http.Cookie
437 Raw string
438 }{
439 {
440- []*Cookie{},
441+ []*http.Cookie{},
442 "",
443 },
444 {
445- []*Cookie{{Name: "cookie-1", Value: "v$1"}},
446+ []*http.Cookie{{Name: "cookie-1", Value: "v$1"}},
447 "cookie-1=v$1",
448 },
449 {
450- []*Cookie{
451+ []*http.Cookie{
452 {Name: "cookie-1", Value: "v$1"},
453 {Name: "cookie-2", Value: "v$2"},
454 {Name: "cookie-3", Value: "v$3"},
455@@ -136,16 +137,16 @@
456 }
457
458 var readSetCookiesTests = []struct {
459- Header Header
460- Cookies []*Cookie
461+ Header http.Header
462+ Cookies []*http.Cookie
463 }{
464 {
465- Header{"Set-Cookie": {"Cookie-1=v$1"}},
466- []*Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}},
467+ http.Header{"Set-Cookie": {"Cookie-1=v$1"}},
468+ []*http.Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}},
469 },
470 {
471- Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}},
472- []*Cookie{{
473+ http.Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}},
474+ []*http.Cookie{{
475 Name: "NID",
476 Value: "99=YsDT5i3E-CXax-",
477 Path: "/",
478@@ -157,8 +158,8 @@
479 }},
480 },
481 {
482- Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}},
483- []*Cookie{{
484+ http.Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}},
485+ []*http.Cookie{{
486 Name: ".ASPXAUTH",
487 Value: "7E3AA",
488 Path: "/",
489@@ -169,8 +170,8 @@
490 }},
491 },
492 {
493- Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}},
494- []*Cookie{{
495+ http.Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}},
496+ []*http.Cookie{{
497 Name: "ASP.NET_SessionId",
498 Value: "foo",
499 Path: "/",
500@@ -207,37 +208,37 @@
501 }
502
503 var readCookiesTests = []struct {
504- Header Header
505+ Header http.Header
506 Filter string
507- Cookies []*Cookie
508+ Cookies []*http.Cookie
509 }{
510 {
511- Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
512- "",
513- []*Cookie{
514- {Name: "Cookie-1", Value: "v$1"},
515- {Name: "c2", Value: "v2"},
516- },
517- },
518- {
519- Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
520- "c2",
521- []*Cookie{
522- {Name: "c2", Value: "v2"},
523- },
524- },
525- {
526- Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
527- "",
528- []*Cookie{
529- {Name: "Cookie-1", Value: "v$1"},
530- {Name: "c2", Value: "v2"},
531- },
532- },
533- {
534- Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
535- "c2",
536- []*Cookie{
537+ http.Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
538+ "",
539+ []*http.Cookie{
540+ {Name: "Cookie-1", Value: "v$1"},
541+ {Name: "c2", Value: "v2"},
542+ },
543+ },
544+ {
545+ http.Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
546+ "c2",
547+ []*http.Cookie{
548+ {Name: "c2", Value: "v2"},
549+ },
550+ },
551+ {
552+ http.Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
553+ "",
554+ []*http.Cookie{
555+ {Name: "Cookie-1", Value: "v$1"},
556+ {Name: "c2", Value: "v2"},
557+ },
558+ },
559+ {
560+ http.Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
561+ "c2",
562+ []*http.Cookie{
563 {Name: "c2", Value: "v2"},
564 },
565 },
566383
567=== modified file 'http13client/export_test.go'384=== modified file 'http13client/export_test.go'
568--- http13client/export_test.go 2014-03-19 20:20:19 +0000385--- http13client/export_test.go 2014-06-20 11:00:47 +0000
569+++ http13client/export_test.go 2014-03-19 22:27:37 +0000386+++ http13client/export_test.go 2014-06-20 12:00:22 +0000
570@@ -9,15 +9,12 @@387@@ -9,15 +9,12 @@
571 388
572 import (389 import (
@@ -599,8 +416,8 @@
599 noProxyEnv.reset()416 noProxyEnv.reset()
600417
601=== modified file 'http13client/header_test.go'418=== modified file 'http13client/header_test.go'
602--- http13client/header_test.go 2014-03-19 20:20:19 +0000419--- http13client/header_test.go 2014-06-20 11:00:47 +0000
603+++ http13client/header_test.go 2014-03-19 22:27:37 +0000420+++ http13client/header_test.go 2014-06-20 12:00:22 +0000
604@@ -6,19 +6,20 @@421@@ -6,19 +6,20 @@
605 422
606 import (423 import (
@@ -731,8 +548,8 @@
731 }548 }
732549
733=== modified file 'http13client/npn_test.go'550=== modified file 'http13client/npn_test.go'
734--- http13client/npn_test.go 2014-03-19 21:38:56 +0000551--- http13client/npn_test.go 2014-06-20 11:00:47 +0000
735+++ http13client/npn_test.go 2014-03-19 22:27:37 +0000552+++ http13client/npn_test.go 2014-06-20 12:05:53 +0000
736@@ -11,13 +11,14 @@553@@ -11,13 +11,14 @@
737 "io"554 "io"
738 "io/ioutil"555 "io/ioutil"
@@ -792,8 +609,8 @@
792 func (w http09Writer) WriteHeader(int) {} // no headers609 func (w http09Writer) WriteHeader(int) {} // no headers
793610
794=== modified file 'http13client/readrequest_test.go'611=== modified file 'http13client/readrequest_test.go'
795--- http13client/readrequest_test.go 2014-03-19 20:20:19 +0000612--- http13client/readrequest_test.go 2014-06-20 11:00:47 +0000
796+++ http13client/readrequest_test.go 2014-03-19 22:27:37 +0000613+++ http13client/readrequest_test.go 2014-06-20 12:00:22 +0000
797@@ -9,6 +9,7 @@614@@ -9,6 +9,7 @@
798 "bytes"615 "bytes"
799 "fmt"616 "fmt"
@@ -909,8 +726,8 @@
909 Close: false,726 Close: false,
910727
911=== modified file 'http13client/request_test.go'728=== modified file 'http13client/request_test.go'
912--- http13client/request_test.go 2014-03-19 21:38:56 +0000729--- http13client/request_test.go 2014-06-20 11:00:47 +0000
913+++ http13client/request_test.go 2014-03-19 22:27:37 +0000730+++ http13client/request_test.go 2014-06-20 12:05:53 +0000
914@@ -12,6 +12,7 @@731@@ -12,6 +12,7 @@
915 "io/ioutil"732 "io/ioutil"
916 "mime/multipart"733 "mime/multipart"
@@ -945,8 +762,26 @@
945+ req.Header = http.Header{"Content-Type": {"text/plain"}}762+ req.Header = http.Header{"Content-Type": {"text/plain"}}
946 multipart, err = req.MultipartReader()763 multipart, err = req.MultipartReader()
947 if multipart != nil {764 if multipart != nil {
948 t.Errorf("unexpected multipart for text/plain")765 t.Error("unexpected multipart for text/plain")
949@@ -159,7 +160,7 @@766@@ -161,7 +162,7 @@
767 func TestParseMultipartForm(t *testing.T) {
768 req := &Request{
769 Method: "POST",
770- Header: Header{"Content-Type": {`multipart/form-data; boundary="foo123"`}},
771+ Header: http.Header{"Content-Type": {`multipart/form-data; boundary="foo123"`}},
772 Body: ioutil.NopCloser(new(bytes.Buffer)),
773 }
774 err := req.ParseMultipartForm(25)
775@@ -169,7 +170,7 @@
776 t.Error("expected multipart EOF, got nil")
777 }
778
779- req.Header = Header{"Content-Type": {"text/plain"}}
780+ req.Header = http.Header{"Content-Type": {"text/plain"}}
781 err = req.ParseMultipartForm(25)
782 if err != ErrNotMultipart {
783 t.Error("expected ErrNotMultipart for text/plain")
784@@ -177,7 +178,7 @@
950 }785 }
951 786
952 func TestRedirect(t *testing.T) {787 func TestRedirect(t *testing.T) {
@@ -957,8 +792,8 @@
957 w.Header().Set("Location", "/foo/")792 w.Header().Set("Location", "/foo/")
958793
959=== modified file 'http13client/requestwrite_test.go'794=== modified file 'http13client/requestwrite_test.go'
960--- http13client/requestwrite_test.go 2014-03-19 20:20:19 +0000795--- http13client/requestwrite_test.go 2014-06-20 11:00:47 +0000
961+++ http13client/requestwrite_test.go 2014-03-19 22:27:37 +0000796+++ http13client/requestwrite_test.go 2014-06-20 12:00:22 +0000
962@@ -10,6 +10,7 @@797@@ -10,6 +10,7 @@
963 "fmt"798 "fmt"
964 "io"799 "io"
@@ -1068,8 +903,8 @@
1068 var braw bytes.Buffer903 var braw bytes.Buffer
1069904
1070=== modified file 'http13client/response_test.go'905=== modified file 'http13client/response_test.go'
1071--- http13client/response_test.go 2014-03-19 20:20:19 +0000906--- http13client/response_test.go 2014-06-20 11:00:47 +0000
1072+++ http13client/response_test.go 2014-03-19 22:27:37 +0000907+++ http13client/response_test.go 2014-06-20 12:05:53 +0000
1073@@ -12,6 +12,7 @@908@@ -12,6 +12,7 @@
1074 "fmt"909 "fmt"
1075 "io"910 "io"
@@ -1078,7 +913,7 @@
1078 "net/url"913 "net/url"
1079 "reflect"914 "reflect"
1080 "regexp"915 "regexp"
1081@@ -44,7 +45,7 @@916@@ -48,7 +49,7 @@
1082 ProtoMajor: 1,917 ProtoMajor: 1,
1083 ProtoMinor: 0,918 ProtoMinor: 0,
1084 Request: dummyReq("GET"),919 Request: dummyReq("GET"),
@@ -1087,7 +922,7 @@
1087 "Connection": {"close"}, // TODO(rsc): Delete?922 "Connection": {"close"}, // TODO(rsc): Delete?
1088 },923 },
1089 Close: true,924 Close: true,
1090@@ -67,7 +68,7 @@925@@ -71,7 +72,7 @@
1091 Proto: "HTTP/1.1",926 Proto: "HTTP/1.1",
1092 ProtoMajor: 1,927 ProtoMajor: 1,
1093 ProtoMinor: 1,928 ProtoMinor: 1,
@@ -1096,7 +931,7 @@
1096 Request: dummyReq("GET"),931 Request: dummyReq("GET"),
1097 Close: true,932 Close: true,
1098 ContentLength: -1,933 ContentLength: -1,
1099@@ -88,7 +89,7 @@934@@ -92,7 +93,7 @@
1100 Proto: "HTTP/1.1",935 Proto: "HTTP/1.1",
1101 ProtoMajor: 1,936 ProtoMajor: 1,
1102 ProtoMinor: 1,937 ProtoMinor: 1,
@@ -1105,7 +940,7 @@
1105 Request: dummyReq("GET"),940 Request: dummyReq("GET"),
1106 Close: false,941 Close: false,
1107 ContentLength: 0,942 ContentLength: 0,
1108@@ -112,7 +113,7 @@943@@ -116,7 +117,7 @@
1109 ProtoMajor: 1,944 ProtoMajor: 1,
1110 ProtoMinor: 0,945 ProtoMinor: 0,
1111 Request: dummyReq("GET"),946 Request: dummyReq("GET"),
@@ -1114,61 +949,61 @@
1114 "Connection": {"close"},949 "Connection": {"close"},
1115 "Content-Length": {"10"},950 "Content-Length": {"10"},
1116 },951 },
1117@@ -142,7 +143,7 @@952@@ -146,7 +147,7 @@
1118 ProtoMajor: 1,953 ProtoMajor: 1,
1119 ProtoMinor: 1,954 ProtoMinor: 1,
1120 Request: dummyReq("GET"),955 Request: dummyReq("GET"),
1121- Header: Header{},956- Header: Header{},
1122+ Header: http.Header{},957+ Header: http.Header{},
1123 Close: false,958 Close: false,
1124 ContentLength: -1,959 ContentLength: -1,
1125 TransferEncoding: []string{"chunked"},960 TransferEncoding: []string{"chunked"},
1126@@ -169,7 +170,7 @@961@@ -173,7 +174,7 @@
1127 ProtoMajor: 1,962 ProtoMajor: 1,
1128 ProtoMinor: 1,963 ProtoMinor: 1,
1129 Request: dummyReq("GET"),964 Request: dummyReq("GET"),
1130- Header: Header{},965- Header: Header{},
1131+ Header: http.Header{},966+ Header: http.Header{},
1132 Close: false,967 Close: false,
1133 ContentLength: -1,968 ContentLength: -1,
1134 TransferEncoding: []string{"chunked"},969 TransferEncoding: []string{"chunked"},
1135@@ -191,7 +192,7 @@970@@ -195,7 +196,7 @@
1136 ProtoMajor: 1,971 ProtoMajor: 1,
1137 ProtoMinor: 1,972 ProtoMinor: 1,
1138 Request: dummyReq("HEAD"),973 Request: dummyReq("HEAD"),
1139- Header: Header{},974- Header: Header{},
1140+ Header: http.Header{},975+ Header: http.Header{},
1141 TransferEncoding: []string{"chunked"},976 TransferEncoding: []string{"chunked"},
1142 Close: false,977 Close: false,
1143 ContentLength: -1,978 ContentLength: -1,
1144@@ -213,7 +214,7 @@979@@ -217,7 +218,7 @@
1145 ProtoMajor: 1,980 ProtoMajor: 1,
1146 ProtoMinor: 0,981 ProtoMinor: 0,
1147 Request: dummyReq("HEAD"),982 Request: dummyReq("HEAD"),
1148- Header: Header{"Content-Length": {"256"}},983- Header: Header{"Content-Length": {"256"}},
1149+ Header: http.Header{"Content-Length": {"256"}},984+ Header: http.Header{"Content-Length": {"256"}},
1150 TransferEncoding: nil,985 TransferEncoding: nil,
1151 Close: true,986 Close: true,
1152 ContentLength: 256,987 ContentLength: 256,
1153@@ -235,7 +236,7 @@988@@ -239,7 +240,7 @@
1154 ProtoMajor: 1,989 ProtoMajor: 1,
1155 ProtoMinor: 1,990 ProtoMinor: 1,
1156 Request: dummyReq("HEAD"),991 Request: dummyReq("HEAD"),
1157- Header: Header{"Content-Length": {"256"}},992- Header: Header{"Content-Length": {"256"}},
1158+ Header: http.Header{"Content-Length": {"256"}},993+ Header: http.Header{"Content-Length": {"256"}},
1159 TransferEncoding: nil,994 TransferEncoding: nil,
1160 Close: false,995 Close: false,
1161 ContentLength: 256,996 ContentLength: 256,
1162@@ -256,7 +257,7 @@997@@ -260,7 +261,7 @@
1163 ProtoMajor: 1,998 ProtoMajor: 1,
1164 ProtoMinor: 0,999 ProtoMinor: 0,
1165 Request: dummyReq("HEAD"),1000 Request: dummyReq("HEAD"),
1166- Header: Header{},1001- Header: Header{},
1167+ Header: http.Header{},1002+ Header: http.Header{},
1168 TransferEncoding: nil,1003 TransferEncoding: nil,
1169 Close: true,1004 Close: true,
1170 ContentLength: -1,1005 ContentLength: -1,
1171@@ -278,7 +279,7 @@1006@@ -282,7 +283,7 @@
1172 ProtoMajor: 1,1007 ProtoMajor: 1,
1173 ProtoMinor: 1,1008 ProtoMinor: 1,
1174 Request: dummyReq("GET"),1009 Request: dummyReq("GET"),
@@ -1177,25 +1012,25 @@
1177 "Content-Length": {"0"},1012 "Content-Length": {"0"},
1178 },1013 },
1179 Close: false,1014 Close: false,
1180@@ -299,7 +300,7 @@1015@@ -303,7 +304,7 @@
1181 ProtoMajor: 1,1016 ProtoMajor: 1,
1182 ProtoMinor: 0,1017 ProtoMinor: 0,
1183 Request: dummyReq("GET"),1018 Request: dummyReq("GET"),
1184- Header: Header{},1019- Header: Header{},
1185+ Header: http.Header{},1020+ Header: http.Header{},
1186 Close: true,1021 Close: true,
1187 ContentLength: -1,1022 ContentLength: -1,
1188 },1023 },
1189@@ -318,7 +319,7 @@1024@@ -322,7 +323,7 @@
1190 ProtoMajor: 1,1025 ProtoMajor: 1,
1191 ProtoMinor: 0,1026 ProtoMinor: 0,
1192 Request: dummyReq("GET"),1027 Request: dummyReq("GET"),
1193- Header: Header{},1028- Header: Header{},
1194+ Header: http.Header{},1029+ Header: http.Header{},
1195 Close: true,1030 Close: true,
1196 ContentLength: -1,1031 ContentLength: -1,
1197 },1032 },
1198@@ -340,7 +341,7 @@1033@@ -344,7 +345,7 @@
1199 ProtoMajor: 1,1034 ProtoMajor: 1,
1200 ProtoMinor: 1,1035 ProtoMinor: 1,
1201 Request: dummyReq("GET"),1036 Request: dummyReq("GET"),
@@ -1204,7 +1039,7 @@
1204 "Content-Type": []string{"multipart/byteranges; boundary=18a75608c8f47cef"},1039 "Content-Type": []string{"multipart/byteranges; boundary=18a75608c8f47cef"},
1205 },1040 },
1206 Close: true,1041 Close: true,
1207@@ -363,7 +364,7 @@1042@@ -367,7 +368,7 @@
1208 Proto: "HTTP/1.0",1043 Proto: "HTTP/1.0",
1209 ProtoMajor: 1,1044 ProtoMajor: 1,
1210 ProtoMinor: 0,1045 ProtoMinor: 0,
@@ -1213,7 +1048,7 @@
1213 "Connection": {"close"}, // TODO(rsc): Delete?1048 "Connection": {"close"}, // TODO(rsc): Delete?
1214 },1049 },
1215 Close: true,1050 Close: true,
1216@@ -545,7 +546,7 @@1051@@ -549,7 +550,7 @@
1217 func TestLocationResponse(t *testing.T) {1052 func TestLocationResponse(t *testing.T) {
1218 for i, tt := range responseLocationTests {1053 for i, tt := range responseLocationTests {
1219 res := new(Response)1054 res := new(Response)
@@ -1222,7 +1057,7 @@
1222 res.Header.Set("Location", tt.location)1057 res.Header.Set("Location", tt.location)
1223 if tt.requrl != "" {1058 if tt.requrl != "" {
1224 res.Request = &Request{}1059 res.Request = &Request{}
1225@@ -626,16 +627,3 @@1060@@ -630,16 +631,3 @@
1226 t.Errorf("ReadResponse = %v; want io.ErrUnexpectedEOF", err)1061 t.Errorf("ReadResponse = %v; want io.ErrUnexpectedEOF", err)
1227 }1062 }
1228 }1063 }
@@ -1241,8 +1076,8 @@
1241-}1076-}
12421077
1243=== modified file 'http13client/responsewrite_test.go'1078=== modified file 'http13client/responsewrite_test.go'
1244--- http13client/responsewrite_test.go 2014-03-19 20:20:19 +00001079--- http13client/responsewrite_test.go 2014-06-20 11:00:47 +0000
1245+++ http13client/responsewrite_test.go 2014-03-19 22:27:37 +00001080+++ http13client/responsewrite_test.go 2014-06-20 12:05:53 +0000
1246@@ -7,6 +7,7 @@1081@@ -7,6 +7,7 @@
1247 import (1082 import (
1248 "bytes"1083 "bytes"
@@ -1257,7 +1092,7 @@
1257 Request: dummyReq("GET"),1092 Request: dummyReq("GET"),
1258- Header: Header{},1093- Header: Header{},
1259+ Header: http.Header{},1094+ Header: http.Header{},
1260 Body: ioutil.NopCloser(bytes.NewBufferString("abcdef")),1095 Body: ioutil.NopCloser(strings.NewReader("abcdef")),
1261 ContentLength: 6,1096 ContentLength: 6,
1262 },1097 },
1263@@ -41,7 +42,7 @@1098@@ -41,7 +42,7 @@
@@ -1270,6 +1105,60 @@
1270 ContentLength: -1,1105 ContentLength: -1,
1271 },1106 },
1272@@ -56,7 +57,7 @@1107@@ -56,7 +57,7 @@
1108 ProtoMajor: 1,
1109 ProtoMinor: 1,
1110 Request: dummyReq("GET"),
1111- Header: Header{},
1112+ Header: http.Header{},
1113 Body: ioutil.NopCloser(strings.NewReader("abcdef")),
1114 ContentLength: -1,
1115 Close: true,
1116@@ -73,7 +74,7 @@
1117 ProtoMajor: 1,
1118 ProtoMinor: 1,
1119 Request: dummyReq11("GET"),
1120- Header: Header{},
1121+ Header: http.Header{},
1122 Body: ioutil.NopCloser(strings.NewReader("abcdef")),
1123 ContentLength: -1,
1124 Close: false,
1125@@ -91,7 +92,7 @@
1126 ProtoMajor: 1,
1127 ProtoMinor: 1,
1128 Request: dummyReq11("GET"),
1129- Header: Header{},
1130+ Header: http.Header{},
1131 Body: ioutil.NopCloser(strings.NewReader("abcdef")),
1132 ContentLength: -1,
1133 TransferEncoding: []string{"chunked"},
1134@@ -108,7 +109,7 @@
1135 ProtoMajor: 1,
1136 ProtoMinor: 1,
1137 Request: dummyReq11("GET"),
1138- Header: Header{},
1139+ Header: http.Header{},
1140 Body: nil,
1141 ContentLength: 0,
1142 Close: false,
1143@@ -124,7 +125,7 @@
1144 ProtoMajor: 1,
1145 ProtoMinor: 1,
1146 Request: dummyReq11("GET"),
1147- Header: Header{},
1148+ Header: http.Header{},
1149 Body: ioutil.NopCloser(strings.NewReader("")),
1150 ContentLength: 0,
1151 Close: false,
1152@@ -140,7 +141,7 @@
1153 ProtoMajor: 1,
1154 ProtoMinor: 1,
1155 Request: dummyReq11("GET"),
1156- Header: Header{},
1157+ Header: http.Header{},
1158 Body: ioutil.NopCloser(strings.NewReader("foo")),
1159 ContentLength: 0,
1160 Close: false,
1161@@ -156,7 +157,7 @@
1273 ProtoMajor: 1,1162 ProtoMajor: 1,
1274 ProtoMinor: 1,1163 ProtoMinor: 1,
1275 Request: dummyReq("GET"),1164 Request: dummyReq("GET"),
@@ -1278,7 +1167,7 @@
1278 Body: ioutil.NopCloser(strings.NewReader("abcdef")),1167 Body: ioutil.NopCloser(strings.NewReader("abcdef")),
1279 ContentLength: 6,1168 ContentLength: 6,
1280 TransferEncoding: []string{"chunked"},1169 TransferEncoding: []string{"chunked"},
1281@@ -77,7 +78,7 @@1170@@ -177,7 +178,7 @@
1282 ProtoMajor: 1,1171 ProtoMajor: 1,
1283 ProtoMinor: 1,1172 ProtoMinor: 1,
1284 Request: dummyReq("GET"),1173 Request: dummyReq("GET"),
@@ -1287,11 +1176,20 @@
1287 "Foo": []string{" Bar\nBaz "},1176 "Foo": []string{" Bar\nBaz "},
1288 },1177 },
1289 Body: nil,1178 Body: nil,
1179@@ -200,7 +201,7 @@
1180 ProtoMajor: 1,
1181 ProtoMinor: 1,
1182 Request: &Request{Method: "POST"},
1183- Header: Header{},
1184+ Header: http.Header{},
1185 ContentLength: 0,
1186 TransferEncoding: nil,
1187 Body: nil,
12901188
1291=== modified file 'http13client/transport_test.go'1189=== modified file 'http13client/transport_test.go'
1292--- http13client/transport_test.go 2014-03-19 21:38:56 +00001190--- http13client/transport_test.go 2014-06-20 11:00:47 +0000
1293+++ http13client/transport_test.go 2014-03-19 22:27:37 +00001191+++ http13client/transport_test.go 2014-06-20 12:05:53 +0000
1294@@ -17,8 +17,8 @@1192@@ -18,8 +18,8 @@
1295 "io/ioutil"1193 "io/ioutil"
1296 "log"1194 "log"
1297 "net"1195 "net"
@@ -1301,7 +1199,7 @@
1301 "net/http/httptest"1199 "net/http/httptest"
1302 "net/url"1200 "net/url"
1303 "os"1201 "os"
1304@@ -34,7 +34,7 @@1202@@ -35,7 +35,7 @@
1305 // and then verify that the final 2 responses get errors back.1203 // and then verify that the final 2 responses get errors back.
1306 1204
1307 // hostPortHandler writes back the client's "host:port".1205 // hostPortHandler writes back the client's "host:port".
@@ -1310,7 +1208,7 @@
1310 if r.FormValue("close") == "true" {1208 if r.FormValue("close") == "true" {
1311 w.Header().Set("Connection", "close")1209 w.Header().Set("Connection", "close")
1312 }1210 }
1313@@ -280,7 +280,7 @@1211@@ -289,7 +289,7 @@
1314 const msg = "foobar"1212 const msg = "foobar"
1315 1213
1316 var addrSeen map[string]int1214 var addrSeen map[string]int
@@ -1319,7 +1217,7 @@
1319 addrSeen[r.RemoteAddr]++1217 addrSeen[r.RemoteAddr]++
1320 if r.URL.Path == "/chunked/" {1218 if r.URL.Path == "/chunked/" {
1321 w.WriteHeader(200)1219 w.WriteHeader(200)
1322@@ -299,7 +299,7 @@1220@@ -308,7 +308,7 @@
1323 wantLen := []int{len(msg), -1}[pi]1221 wantLen := []int{len(msg), -1}[pi]
1324 addrSeen = make(map[string]int)1222 addrSeen = make(map[string]int)
1325 for i := 0; i < 3; i++ {1223 for i := 0; i < 3; i++ {
@@ -1328,7 +1226,7 @@
1328 if err != nil {1226 if err != nil {
1329 t.Errorf("Get %s: %v", path, err)1227 t.Errorf("Get %s: %v", path, err)
1330 continue1228 continue
1331@@ -329,7 +329,7 @@1229@@ -338,7 +338,7 @@
1332 defer afterTest(t)1230 defer afterTest(t)
1333 resch := make(chan string)1231 resch := make(chan string)
1334 gotReq := make(chan bool)1232 gotReq := make(chan bool)
@@ -1337,7 +1235,7 @@
1337 gotReq <- true1235 gotReq <- true
1338 msg := <-resch1236 msg := <-resch
1339 _, err := w.Write([]byte(msg))1237 _, err := w.Write([]byte(msg))
1340@@ -457,12 +457,12 @@1238@@ -466,12 +466,12 @@
1341 if testing.Short() {1239 if testing.Short() {
1342 t.Skip("skipping test in short mode")1240 t.Skip("skipping test in short mode")
1343 }1241 }
@@ -1353,7 +1251,7 @@
1353 buf.Flush()1251 buf.Flush()
1354 conn.Close()1252 conn.Close()
1355 }))1253 }))
1356@@ -510,7 +510,7 @@1254@@ -519,7 +519,7 @@
1357 // with no bodies properly1255 // with no bodies properly
1358 func TestTransportHeadResponses(t *testing.T) {1256 func TestTransportHeadResponses(t *testing.T) {
1359 defer afterTest(t)1257 defer afterTest(t)
@@ -1362,7 +1260,7 @@
1362 if r.Method != "HEAD" {1260 if r.Method != "HEAD" {
1363 panic("expected HEAD; got " + r.Method)1261 panic("expected HEAD; got " + r.Method)
1364 }1262 }
1365@@ -545,7 +545,7 @@1263@@ -554,7 +554,7 @@
1366 // on responses to HEAD requests.1264 // on responses to HEAD requests.
1367 func TestTransportHeadChunkedResponse(t *testing.T) {1265 func TestTransportHeadChunkedResponse(t *testing.T) {
1368 defer afterTest(t)1266 defer afterTest(t)
@@ -1371,7 +1269,7 @@
1371 if r.Method != "HEAD" {1269 if r.Method != "HEAD" {
1372 panic("expected HEAD; got " + r.Method)1270 panic("expected HEAD; got " + r.Method)
1373 }1271 }
1374@@ -588,7 +588,7 @@1272@@ -597,7 +597,7 @@
1375 func TestRoundTripGzip(t *testing.T) {1273 func TestRoundTripGzip(t *testing.T) {
1376 defer afterTest(t)1274 defer afterTest(t)
1377 const responseBody = "test response body"1275 const responseBody = "test response body"
@@ -1380,7 +1278,7 @@
1380 accept := req.Header.Get("Accept-Encoding")1278 accept := req.Header.Get("Accept-Encoding")
1381 if expect := req.FormValue("expect_accept"); accept != expect {1279 if expect := req.FormValue("expect_accept"); accept != expect {
1382 t.Errorf("in handler, test %v: Accept-Encoding = %q, want %q",1280 t.Errorf("in handler, test %v: Accept-Encoding = %q, want %q",
1383@@ -647,7 +647,7 @@1281@@ -656,7 +656,7 @@
1384 defer afterTest(t)1282 defer afterTest(t)
1385 const testString = "The test string aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"1283 const testString = "The test string aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1386 const nRandBytes = 1024 * 10241284 const nRandBytes = 1024 * 1024
@@ -1389,7 +1287,7 @@
1389 if req.Method == "HEAD" {1287 if req.Method == "HEAD" {
1390 if g := req.Header.Get("Accept-Encoding"); g != "" {1288 if g := req.Header.Get("Accept-Encoding"); g != "" {
1391 t.Errorf("HEAD request sent with Accept-Encoding of %q; want none", g)1289 t.Errorf("HEAD request sent with Accept-Encoding of %q; want none", g)
1392@@ -742,11 +742,11 @@1290@@ -751,11 +751,11 @@
1393 func TestTransportProxy(t *testing.T) {1291 func TestTransportProxy(t *testing.T) {
1394 defer afterTest(t)1292 defer afterTest(t)
1395 ch := make(chan string, 1)1293 ch := make(chan string, 1)
@@ -1403,7 +1301,7 @@
1403 ch <- "proxy for " + r.URL.String()1301 ch <- "proxy for " + r.URL.String()
1404 }))1302 }))
1405 defer proxy.Close()1303 defer proxy.Close()
1406@@ -770,7 +770,7 @@1304@@ -779,7 +779,7 @@
1407 // Content-Encoding is removed.1305 // Content-Encoding is removed.
1408 func TestTransportGzipRecursive(t *testing.T) {1306 func TestTransportGzipRecursive(t *testing.T) {
1409 defer afterTest(t)1307 defer afterTest(t)
@@ -1412,7 +1310,16 @@
1412 w.Header().Set("Content-Encoding", "gzip")1310 w.Header().Set("Content-Encoding", "gzip")
1413 w.Write(rgz)1311 w.Write(rgz)
1414 }))1312 }))
1415@@ -802,7 +802,7 @@1313@@ -807,7 +807,7 @@
1314 // a short gzip body
1315 func TestTransportGzipShort(t *testing.T) {
1316 defer afterTest(t)
1317- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
1318+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1319 w.Header().Set("Content-Encoding", "gzip")
1320 w.Write([]byte{0x1f, 0x8b})
1321 }))
1322@@ -838,7 +838,7 @@
1416 defer afterTest(t)1323 defer afterTest(t)
1417 gotReqCh := make(chan bool)1324 gotReqCh := make(chan bool)
1418 unblockCh := make(chan bool)1325 unblockCh := make(chan bool)
@@ -1421,7 +1328,7 @@
1421 gotReqCh <- true1328 gotReqCh <- true
1422 <-unblockCh1329 <-unblockCh
1423 w.Header().Set("Content-Length", "0")1330 w.Header().Set("Content-Length", "0")
1424@@ -869,7 +869,7 @@1331@@ -905,7 +905,7 @@
1425 t.Skip("skipping test; see http://golang.org/issue/7237")1332 t.Skip("skipping test; see http://golang.org/issue/7237")
1426 }1333 }
1427 defer afterTest(t)1334 defer afterTest(t)
@@ -1430,7 +1337,7 @@
1430 }))1337 }))
1431 defer ts.Close()1338 defer ts.Close()
1432 1339
1433@@ -912,7 +912,7 @@1340@@ -948,7 +948,7 @@
1434 c := &Client{Transport: tr}1341 c := &Client{Transport: tr}
1435 1342
1436 unblockCh := make(chan bool, 1)1343 unblockCh := make(chan bool, 1)
@@ -1439,7 +1346,7 @@
1439 <-unblockCh1346 <-unblockCh
1440 tr.CloseIdleConnections()1347 tr.CloseIdleConnections()
1441 }))1348 }))
1442@@ -939,7 +939,7 @@1349@@ -975,7 +975,7 @@
1443 func TestIssue3644(t *testing.T) {1350 func TestIssue3644(t *testing.T) {
1444 defer afterTest(t)1351 defer afterTest(t)
1445 const numFoos = 50001352 const numFoos = 5000
@@ -1448,7 +1355,7 @@
1448 w.Header().Set("Connection", "close")1355 w.Header().Set("Connection", "close")
1449 for i := 0; i < numFoos; i++ {1356 for i := 0; i < numFoos; i++ {
1450 w.Write([]byte("foo "))1357 w.Write([]byte("foo "))
1451@@ -967,8 +967,8 @@1358@@ -1003,8 +1003,8 @@
1452 func TestIssue3595(t *testing.T) {1359 func TestIssue3595(t *testing.T) {
1453 defer afterTest(t)1360 defer afterTest(t)
1454 const deniedMsg = "sorry, denied."1361 const deniedMsg = "sorry, denied."
@@ -1459,7 +1366,7 @@
1459 }))1366 }))
1460 defer ts.Close()1367 defer ts.Close()
1461 tr := &Transport{}1368 tr := &Transport{}
1462@@ -991,7 +991,7 @@1369@@ -1027,7 +1027,7 @@
1463 // "client fails to handle requests with no body and chunked encoding"1370 // "client fails to handle requests with no body and chunked encoding"
1464 func TestChunkedNoContent(t *testing.T) {1371 func TestChunkedNoContent(t *testing.T) {
1465 defer afterTest(t)1372 defer afterTest(t)
@@ -1468,7 +1375,7 @@
1468 w.WriteHeader(StatusNoContent)1375 w.WriteHeader(StatusNoContent)
1469 }))1376 }))
1470 defer ts.Close()1377 defer ts.Close()
1471@@ -1019,7 +1019,7 @@1378@@ -1055,7 +1055,7 @@
1472 maxProcs, numReqs = 4, 501379 maxProcs, numReqs = 4, 50
1473 }1380 }
1474 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(maxProcs))1381 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(maxProcs))
@@ -1477,7 +1384,7 @@
1477 fmt.Fprintf(w, "%v", r.FormValue("echo"))1384 fmt.Fprintf(w, "%v", r.FormValue("echo"))
1478 }))1385 }))
1479 defer ts.Close()1386 defer ts.Close()
1480@@ -1080,8 +1080,8 @@1387@@ -1116,8 +1116,8 @@
1481 }1388 }
1482 defer afterTest(t)1389 defer afterTest(t)
1483 const debug = false1390 const debug = false
@@ -1488,7 +1395,7 @@
1488 io.Copy(w, neverEnding('a'))1395 io.Copy(w, neverEnding('a'))
1489 })1396 })
1490 ts := httptest.NewServer(mux)1397 ts := httptest.NewServer(mux)
1491@@ -1144,11 +1144,11 @@1398@@ -1180,11 +1180,11 @@
1492 }1399 }
1493 defer afterTest(t)1400 defer afterTest(t)
1494 const debug = false1401 const debug = false
@@ -1503,20 +1410,22 @@
1503 defer r.Body.Close()1410 defer r.Body.Close()
1504 io.Copy(ioutil.Discard, r.Body)1411 io.Copy(ioutil.Discard, r.Body)
1505 })1412 })
1506@@ -1214,9 +1214,9 @@1413@@ -1251,11 +1251,11 @@
1507 if testing.Short() {
1508 t.Skip("skipping timeout test in -short mode")1414 t.Skip("skipping timeout test in -short mode")
1509 }1415 }
1416 inHandler := make(chan bool, 1)
1510- mux := NewServeMux()1417- mux := NewServeMux()
1511- mux.HandleFunc("/fast", func(w ResponseWriter, r *Request) {})1418- mux.HandleFunc("/fast", func(w ResponseWriter, r *Request) {
1419+ mux := http.NewServeMux()
1420+ mux.HandleFunc("/fast", func(w http.ResponseWriter, r *http.Request) {
1421 inHandler <- true
1422 })
1512- mux.HandleFunc("/slow", func(w ResponseWriter, r *Request) {1423- mux.HandleFunc("/slow", func(w ResponseWriter, r *Request) {
1513+ mux := http.NewServeMux()
1514+ mux.HandleFunc("/fast", func(w http.ResponseWriter, r *http.Request) {})
1515+ mux.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) {1424+ mux.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) {
1425 inHandler <- true
1516 time.Sleep(2 * time.Second)1426 time.Sleep(2 * time.Second)
1517 })1427 })
1518 ts := httptest.NewServer(mux)1428@@ -1322,9 +1322,9 @@
1519@@ -1276,9 +1276,9 @@
1520 t.Skip("skipping test in -short mode")1429 t.Skip("skipping test in -short mode")
1521 }1430 }
1522 unblockc := make(chan bool)1431 unblockc := make(chan bool)
@@ -1528,7 +1437,7 @@
1528 <-unblockc1437 <-unblockc
1529 }))1438 }))
1530 defer ts.Close()1439 defer ts.Close()
1531@@ -1386,14 +1386,14 @@1440@@ -1431,14 +1431,14 @@
1532 defer afterTest(t)1441 defer afterTest(t)
1533 writeErr := make(chan error, 1)1442 writeErr := make(chan error, 1)
1534 msg := []byte("young\n")1443 msg := []byte("young\n")
@@ -1545,7 +1454,7 @@
1545 }1454 }
1546 }))1455 }))
1547 defer ts.Close()1456 defer ts.Close()
1548@@ -1449,7 +1449,7 @@1457@@ -1494,7 +1494,7 @@
1549 res := &Response{1458 res := &Response{
1550 Status: "200 OK",1459 Status: "200 OK",
1551 StatusCode: 200,1460 StatusCode: 200,
@@ -1554,7 +1463,7 @@
1554 Body: ioutil.NopCloser(strings.NewReader("You wanted " + req.URL.String())),1463 Body: ioutil.NopCloser(strings.NewReader("You wanted " + req.URL.String())),
1555 }1464 }
1556 return res, nil1465 return res, nil
1557@@ -1478,7 +1478,7 @@1466@@ -1523,7 +1523,7 @@
1558 defer afterTest(t)1467 defer afterTest(t)
1559 tr := &Transport{}1468 tr := &Transport{}
1560 _, err := tr.RoundTrip(&Request{1469 _, err := tr.RoundTrip(&Request{
@@ -1563,7 +1472,7 @@
1563 URL: &url.URL{1472 URL: &url.URL{
1564 Scheme: "http",1473 Scheme: "http",
1565 },1474 },
1566@@ -1492,14 +1492,14 @@1475@@ -1537,14 +1537,14 @@
1567 func TestTransportSocketLateBinding(t *testing.T) {1476 func TestTransportSocketLateBinding(t *testing.T) {
1568 defer afterTest(t)1477 defer afterTest(t)
1569 1478
@@ -1582,7 +1491,7 @@
1582 w.Header().Set("bar-ipport", r.RemoteAddr)1491 w.Header().Set("bar-ipport", r.RemoteAddr)
1583 })1492 })
1584 ts := httptest.NewServer(mux)1493 ts := httptest.NewServer(mux)
1585@@ -1720,7 +1720,7 @@1494@@ -1767,7 +1767,7 @@
1586 var mu sync.Mutex1495 var mu sync.Mutex
1587 var n int1496 var n int
1588 1497
@@ -1591,7 +1500,7 @@
1591 mu.Lock()1500 mu.Lock()
1592 n++1501 n++
1593 mu.Unlock()1502 mu.Unlock()
1594@@ -1756,7 +1756,7 @@1503@@ -1803,7 +1803,7 @@
1595 // then closes it.1504 // then closes it.
1596 func TestTransportClosesRequestBody(t *testing.T) {1505 func TestTransportClosesRequestBody(t *testing.T) {
1597 defer afterTest(t)1506 defer afterTest(t)
@@ -1600,4 +1509,49 @@
1600 io.Copy(ioutil.Discard, r.Body)1509 io.Copy(ioutil.Discard, r.Body)
1601 }))1510 }))
1602 defer ts.Close()1511 defer ts.Close()
1512@@ -1890,9 +1890,9 @@
1513 t.Skip("skipping flaky test on Windows; golang.org/issue/7634")
1514 }
1515 closedc := make(chan bool, 1)
1516- ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) {
1517+ ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1518 if strings.Contains(r.URL.Path, "/keep-alive-then-die") {
1519- conn, _, _ := w.(Hijacker).Hijack()
1520+ conn, _, _ := w.(http.Hijacker).Hijack()
1521 conn.Write([]byte("HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nfoo"))
1522 conn.Close()
1523 closedc <- true
1524@@ -1994,12 +1994,12 @@
1525 }
1526 defer closeConn()
1527
1528- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
1529+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1530 if r.Method == "GET" {
1531 io.WriteString(w, "bar")
1532 return
1533 }
1534- conn, _, _ := w.(Hijacker).Hijack()
1535+ conn, _, _ := w.(http.Hijacker).Hijack()
1536 sconn.Lock()
1537 sconn.c = conn
1538 sconn.Unlock()
1539@@ -2056,7 +2056,7 @@
1540 }
1541 defer afterTest(t)
1542 readBody := make(chan error, 1)
1543- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
1544+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1545 _, err := ioutil.ReadAll(r.Body)
1546 readBody <- err
1547 }))
1548@@ -2098,7 +2098,7 @@
1549 }
1550 }
1551
1552-func wantBody(res *http.Response, err error, want string) error {
1553+func wantBody(res *Response, err error, want string) error {
1554 if err != nil {
1555 return err
1556 }
16031557
16041558
=== modified file 'http13client/_using.txt'
--- http13client/_using.txt 2014-03-20 12:20:01 +0000
+++ http13client/_using.txt 2014-06-20 13:02:58 +0000
@@ -1,5 +1,5 @@
1parent: 19512:32c32aef2a41 tip1parent: 20169:9895f9e36435 go1.3 release
2 test: enable bug385_32 test on amd64p32.2 go1.3
3branch: default3branch: release-branch.go1.3
4commit: (clean)4commit: (clean)
5update: (current)5update: (current)
66
=== modified file 'http13client/client.go'
--- http13client/client.go 2014-03-20 09:26:28 +0000
+++ http13client/client.go 2014-06-20 13:02:58 +0000
@@ -92,8 +92,9 @@
92 // authentication, or cookies.92 // authentication, or cookies.
93 //93 //
94 // RoundTrip should not modify the request, except for94 // RoundTrip should not modify the request, except for
95 // consuming and closing the Body. The request's URL and95 // consuming and closing the Body, including on errors. The
96 // Header fields are guaranteed to be initialized.96 // request's URL and Header fields are guaranteed to be
97 // initialized.
97 RoundTrip(*Request) (*Response, error)98 RoundTrip(*Request) (*Response, error)
98}99}
99100
@@ -141,6 +142,9 @@
141// (typically Transport) may not be able to re-use a persistent TCP142// (typically Transport) may not be able to re-use a persistent TCP
142// connection to the server for a subsequent "keep-alive" request.143// connection to the server for a subsequent "keep-alive" request.
143//144//
145// The request Body, if non-nil, will be closed by the underlying
146// Transport, even on errors.
147//
144// Generally Get, Post, or PostForm will be used instead of Do.148// Generally Get, Post, or PostForm will be used instead of Do.
145func (c *Client) Do(req *Request) (resp *Response, err error) {149func (c *Client) Do(req *Request) (resp *Response, err error) {
146 if req.Method == "GET" || req.Method == "HEAD" {150 if req.Method == "GET" || req.Method == "HEAD" {
@@ -163,14 +167,17 @@
163// Caller should close resp.Body when done reading from it.167// Caller should close resp.Body when done reading from it.
164func send(req *Request, t RoundTripper) (resp *Response, err error) {168func send(req *Request, t RoundTripper) (resp *Response, err error) {
165 if t == nil {169 if t == nil {
170 req.closeBody()
166 return nil, errors.New("http: no Client.Transport or DefaultTransport")171 return nil, errors.New("http: no Client.Transport or DefaultTransport")
167 }172 }
168173
169 if req.URL == nil {174 if req.URL == nil {
175 req.closeBody()
170 return nil, errors.New("http: nil Request.URL")176 return nil, errors.New("http: nil Request.URL")
171 }177 }
172178
173 if req.RequestURI != "" {179 if req.RequestURI != "" {
180 req.closeBody()
174 return nil, errors.New("http: Request.RequestURI can't be set in client requests.")181 return nil, errors.New("http: Request.RequestURI can't be set in client requests.")
175 }182 }
176183
@@ -278,6 +285,7 @@
278 var via []*Request285 var via []*Request
279286
280 if ireq.URL == nil {287 if ireq.URL == nil {
288 ireq.closeBody()
281 return nil, errors.New("http: nil Request.URL")289 return nil, errors.New("http: nil Request.URL")
282 }290 }
283291
@@ -400,7 +408,7 @@
400// Caller should close resp.Body when done reading from it.408// Caller should close resp.Body when done reading from it.
401//409//
402// If the provided body is also an io.Closer, it is closed after the410// If the provided body is also an io.Closer, it is closed after the
403// body is successfully written to the server.411// request.
404func (c *Client) Post(url string, bodyType string, body io.Reader) (resp *Response, err error) {412func (c *Client) Post(url string, bodyType string, body io.Reader) (resp *Response, err error) {
405 req, err := NewRequest("POST", url, body)413 req, err := NewRequest("POST", url, body)
406 if err != nil {414 if err != nil {
407415
=== modified file 'http13client/client_test.go'
--- http13client/client_test.go 2014-03-20 09:26:28 +0000
+++ http13client/client_test.go 2014-06-20 13:02:58 +0000
@@ -20,6 +20,8 @@
20 "net/http"20 "net/http"
21 "net/http/httptest"21 "net/http/httptest"
22 "net/url"22 "net/url"
23 "reflect"
24 "sort"
23 "strconv"25 "strconv"
24 "strings"26 "strings"
25 "sync"27 "sync"
@@ -877,3 +879,93 @@
877 t.Fatal("server saw different client ports before & after the redirect")879 t.Fatal("server saw different client ports before & after the redirect")
878 }880 }
879}881}
882
883// eofReaderFunc is an io.Reader that runs itself, and then returns io.EOF.
884type eofReaderFunc func()
885
886func (f eofReaderFunc) Read(p []byte) (n int, err error) {
887 f()
888 return 0, io.EOF
889}
890
891func TestClientTrailers(t *testing.T) {
892 defer afterTest(t)
893 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
894 w.Header().Set("Connection", "close")
895 w.Header().Set("Trailer", "Server-Trailer-A, Server-Trailer-B")
896 w.Header().Add("Trailer", "Server-Trailer-C")
897
898 var decl []string
899 for k := range r.Trailer {
900 decl = append(decl, k)
901 }
902 sort.Strings(decl)
903
904 slurp, err := ioutil.ReadAll(r.Body)
905 if err != nil {
906 t.Errorf("Server reading request body: %v", err)
907 }
908 if string(slurp) != "foo" {
909 t.Errorf("Server read request body %q; want foo", slurp)
910 }
911 if r.Trailer == nil {
912 io.WriteString(w, "nil Trailer")
913 } else {
914 fmt.Fprintf(w, "decl: %v, vals: %s, %s",
915 decl,
916 r.Trailer.Get("Client-Trailer-A"),
917 r.Trailer.Get("Client-Trailer-B"))
918 }
919
920 // TODO: golang.org/issue/7759: there's no way yet for
921 // the server to set trailers without hijacking, so do
922 // that for now, just to test the client. Later, in
923 // Go 1.4, it should be implicit that any mutations
924 // to w.Header() after the initial write are the
925 // trailers to be sent, if and only if they were
926 // previously declared with w.Header().Set("Trailer",
927 // ..keys..)
928 w.(http.Flusher).Flush()
929 conn, buf, _ := w.(http.Hijacker).Hijack()
930 t := http.Header{}
931 t.Set("Server-Trailer-A", "valuea")
932 t.Set("Server-Trailer-C", "valuec") // skipping B
933 buf.WriteString("0\r\n") // eof
934 t.Write(buf)
935 buf.WriteString("\r\n") // end of trailers
936 buf.Flush()
937 conn.Close()
938 }))
939 defer ts.Close()
940
941 var req *Request
942 req, _ = NewRequest("POST", ts.URL, io.MultiReader(
943 eofReaderFunc(func() {
944 req.Trailer["Client-Trailer-A"] = []string{"valuea"}
945 }),
946 strings.NewReader("foo"),
947 eofReaderFunc(func() {
948 req.Trailer["Client-Trailer-B"] = []string{"valueb"}
949 }),
950 ))
951 req.Trailer = http.Header{
952 "Client-Trailer-A": nil, // to be set later
953 "Client-Trailer-B": nil, // to be set later
954 }
955 req.ContentLength = -1
956 res, err := DefaultClient.Do(req)
957 if err != nil {
958 t.Fatal(err)
959 }
960 if err := wantBody(res, err, "decl: [Client-Trailer-A Client-Trailer-B], vals: valuea, valueb"); err != nil {
961 t.Error(err)
962 }
963 want := http.Header{
964 "Server-Trailer-A": []string{"valuea"},
965 "Server-Trailer-B": nil,
966 "Server-Trailer-C": []string{"valuec"},
967 }
968 if !reflect.DeepEqual(res.Trailer, want) {
969 t.Errorf("Response trailers = %#v; want %#v", res.Trailer, want)
970 }
971}
880972
=== modified file 'http13client/cookie.go'
--- http13client/cookie.go 2014-03-19 23:13:58 +0000
+++ http13client/cookie.go 2014-06-20 13:02:58 +0000
@@ -55,11 +55,7 @@
55 attr, val = attr[:j], attr[j+1:]55 attr, val = attr[:j], attr[j+1:]
56 }56 }
57 lowerAttr := strings.ToLower(attr)57 lowerAttr := strings.ToLower(attr)
58 parseCookieValueFn := parseCookieValue58 val, success = parseCookieValue(val)
59 if lowerAttr == "expires" {
60 parseCookieValueFn = parseCookieExpiresValue
61 }
62 val, success = parseCookieValueFn(val)
63 if !success {59 if !success {
64 c.Unparsed = append(c.Unparsed, parts[i])60 c.Unparsed = append(c.Unparsed, parts[i])
65 continue61 continue
@@ -230,12 +226,23 @@
230// ; US-ASCII characters excluding CTLs,226// ; US-ASCII characters excluding CTLs,
231// ; whitespace DQUOTE, comma, semicolon,227// ; whitespace DQUOTE, comma, semicolon,
232// ; and backslash228// ; and backslash
229// We loosen this as spaces and commas are common in cookie values
230// but we produce a quoted cookie-value in when value starts or ends
231// with a comma or space.
232// See http://golang.org/issue/7243 for the discussion.
233func sanitizeCookieValue(v string) string {233func sanitizeCookieValue(v string) string {
234 return sanitizeOrWarn("Cookie.Value", validCookieValueByte, v)234 v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v)
235 if len(v) == 0 {
236 return v
237 }
238 if v[0] == ' ' || v[0] == ',' || v[len(v)-1] == ' ' || v[len(v)-1] == ',' {
239 return `"` + v + `"`
240 }
241 return v
235}242}
236243
237func validCookieValueByte(b byte) bool {244func validCookieValueByte(b byte) bool {
238 return 0x20 < b && b < 0x7f && b != '"' && b != ',' && b != ';' && b != '\\'245 return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\'
239}246}
240247
241// path-av = "Path=" path-value248// path-av = "Path=" path-value
@@ -270,38 +277,13 @@
270 return string(buf)277 return string(buf)
271}278}
272279
273func unquoteCookieValue(v string) string {
274 if len(v) > 1 && v[0] == '"' && v[len(v)-1] == '"' {
275 return v[1 : len(v)-1]
276 }
277 return v
278}
279
280func isCookieByte(c byte) bool {
281 switch {
282 case c == 0x21, 0x23 <= c && c <= 0x2b, 0x2d <= c && c <= 0x3a,
283 0x3c <= c && c <= 0x5b, 0x5d <= c && c <= 0x7e:
284 return true
285 }
286 return false
287}
288
289func isCookieExpiresByte(c byte) (ok bool) {
290 return isCookieByte(c) || c == ',' || c == ' '
291}
292
293func parseCookieValue(raw string) (string, bool) {280func parseCookieValue(raw string) (string, bool) {
294 return parseCookieValueUsing(raw, isCookieByte)281 // Strip the quotes, if present.
295}282 if len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' {
296283 raw = raw[1 : len(raw)-1]
297func parseCookieExpiresValue(raw string) (string, bool) {284 }
298 return parseCookieValueUsing(raw, isCookieExpiresByte)
299}
300
301func parseCookieValueUsing(raw string, validByte func(byte) bool) (string, bool) {
302 raw = unquoteCookieValue(raw)
303 for i := 0; i < len(raw); i++ {285 for i := 0; i < len(raw); i++ {
304 if !validByte(raw[i]) {286 if !validCookieValueByte(raw[i]) {
305 return "", false287 return "", false
306 }288 }
307 }289 }
308290
=== removed file 'http13client/cookie_test.go'
--- http13client/cookie_test.go 2014-03-19 23:13:58 +0000
+++ http13client/cookie_test.go 1970-01-01 00:00:00 +0000
@@ -1,304 +0,0 @@
1// Copyright 2010 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package http
6
7import (
8 "bytes"
9 "encoding/json"
10 "fmt"
11 "log"
12 "net/http"
13 "os"
14 "reflect"
15 "strings"
16 "testing"
17 "time"
18)
19
20var writeSetCookiesTests = []struct {
21 Cookie *http.Cookie
22 Raw string
23}{
24 {
25 &http.Cookie{Name: "cookie-1", Value: "v$1"},
26 "cookie-1=v$1",
27 },
28 {
29 &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600},
30 "cookie-2=two; Max-Age=3600",
31 },
32 {
33 &http.Cookie{Name: "cookie-3", Value: "three", Domain: ".example.com"},
34 "cookie-3=three; Domain=example.com",
35 },
36 {
37 &http.Cookie{Name: "cookie-4", Value: "four", Path: "/restricted/"},
38 "cookie-4=four; Path=/restricted/",
39 },
40 {
41 &http.Cookie{Name: "cookie-5", Value: "five", Domain: "wrong;bad.abc"},
42 "cookie-5=five",
43 },
44 {
45 &http.Cookie{Name: "cookie-6", Value: "six", Domain: "bad-.abc"},
46 "cookie-6=six",
47 },
48 {
49 &http.Cookie{Name: "cookie-7", Value: "seven", Domain: "127.0.0.1"},
50 "cookie-7=seven; Domain=127.0.0.1",
51 },
52 {
53 &http.Cookie{Name: "cookie-8", Value: "eight", Domain: "::1"},
54 "cookie-8=eight",
55 },
56}
57
58func TestWriteSetCookies(t *testing.T) {
59 defer log.SetOutput(os.Stderr)
60 var logbuf bytes.Buffer
61 log.SetOutput(&logbuf)
62
63 for i, tt := range writeSetCookiesTests {
64 if g, e := tt.Cookie.String(), tt.Raw; g != e {
65 t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, e, g)
66 continue
67 }
68 }
69
70 if got, sub := logbuf.String(), "dropping domain attribute"; !strings.Contains(got, sub) {
71 t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got)
72 }
73}
74
75type headerOnlyResponseWriter http.Header
76
77func (ho headerOnlyResponseWriter) Header() http.Header {
78 return http.Header(ho)
79}
80
81func (ho headerOnlyResponseWriter) Write([]byte) (int, error) {
82 panic("NOIMPL")
83}
84
85func (ho headerOnlyResponseWriter) WriteHeader(int) {
86 panic("NOIMPL")
87}
88
89func TestSetCookie(t *testing.T) {
90 m := make(http.Header)
91 http.SetCookie(headerOnlyResponseWriter(m), &http.Cookie{Name: "cookie-1", Value: "one", Path: "/restricted/"})
92 http.SetCookie(headerOnlyResponseWriter(m), &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600})
93 if l := len(m["Set-Cookie"]); l != 2 {
94 t.Fatalf("expected %d cookies, got %d", 2, l)
95 }
96 if g, e := m["Set-Cookie"][0], "cookie-1=one; Path=/restricted/"; g != e {
97 t.Errorf("cookie #1: want %q, got %q", e, g)
98 }
99 if g, e := m["Set-Cookie"][1], "cookie-2=two; Max-Age=3600"; g != e {
100 t.Errorf("cookie #2: want %q, got %q", e, g)
101 }
102}
103
104var addCookieTests = []struct {
105 Cookies []*http.Cookie
106 Raw string
107}{
108 {
109 []*http.Cookie{},
110 "",
111 },
112 {
113 []*http.Cookie{{Name: "cookie-1", Value: "v$1"}},
114 "cookie-1=v$1",
115 },
116 {
117 []*http.Cookie{
118 {Name: "cookie-1", Value: "v$1"},
119 {Name: "cookie-2", Value: "v$2"},
120 {Name: "cookie-3", Value: "v$3"},
121 },
122 "cookie-1=v$1; cookie-2=v$2; cookie-3=v$3",
123 },
124}
125
126func TestAddCookie(t *testing.T) {
127 for i, tt := range addCookieTests {
128 req, _ := NewRequest("GET", "http://example.com/", nil)
129 for _, c := range tt.Cookies {
130 req.AddCookie(c)
131 }
132 if g := req.Header.Get("Cookie"); g != tt.Raw {
133 t.Errorf("Test %d:\nwant: %s\n got: %s\n", i, tt.Raw, g)
134 continue
135 }
136 }
137}
138
139var readSetCookiesTests = []struct {
140 Header http.Header
141 Cookies []*http.Cookie
142}{
143 {
144 http.Header{"Set-Cookie": {"Cookie-1=v$1"}},
145 []*http.Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}},
146 },
147 {
148 http.Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}},
149 []*http.Cookie{{
150 Name: "NID",
151 Value: "99=YsDT5i3E-CXax-",
152 Path: "/",
153 Domain: ".google.ch",
154 HttpOnly: true,
155 Expires: time.Date(2011, 11, 23, 1, 5, 3, 0, time.UTC),
156 RawExpires: "Wed, 23-Nov-2011 01:05:03 GMT",
157 Raw: "NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly",
158 }},
159 },
160 {
161 http.Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}},
162 []*http.Cookie{{
163 Name: ".ASPXAUTH",
164 Value: "7E3AA",
165 Path: "/",
166 Expires: time.Date(2012, 3, 7, 14, 25, 6, 0, time.UTC),
167 RawExpires: "Wed, 07-Mar-2012 14:25:06 GMT",
168 HttpOnly: true,
169 Raw: ".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly",
170 }},
171 },
172 {
173 http.Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}},
174 []*http.Cookie{{
175 Name: "ASP.NET_SessionId",
176 Value: "foo",
177 Path: "/",
178 HttpOnly: true,
179 Raw: "ASP.NET_SessionId=foo; path=/; HttpOnly",
180 }},
181 },
182
183 // TODO(bradfitz): users have reported seeing this in the
184 // wild, but do browsers handle it? RFC 6265 just says "don't
185 // do that" (section 3) and then never mentions header folding
186 // again.
187 // Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly, .ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}},
188}
189
190func toJSON(v interface{}) string {
191 b, err := json.Marshal(v)
192 if err != nil {
193 return fmt.Sprintf("%#v", v)
194 }
195 return string(b)
196}
197
198func TestReadSetCookies(t *testing.T) {
199 for i, tt := range readSetCookiesTests {
200 for n := 0; n < 2; n++ { // to verify readSetCookies doesn't mutate its input
201 c := readSetCookies(tt.Header)
202 if !reflect.DeepEqual(c, tt.Cookies) {
203 t.Errorf("#%d readSetCookies: have\n%s\nwant\n%s\n", i, toJSON(c), toJSON(tt.Cookies))
204 continue
205 }
206 }
207 }
208}
209
210var readCookiesTests = []struct {
211 Header http.Header
212 Filter string
213 Cookies []*http.Cookie
214}{
215 {
216 http.Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
217 "",
218 []*http.Cookie{
219 {Name: "Cookie-1", Value: "v$1"},
220 {Name: "c2", Value: "v2"},
221 },
222 },
223 {
224 http.Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
225 "c2",
226 []*http.Cookie{
227 {Name: "c2", Value: "v2"},
228 },
229 },
230 {
231 http.Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
232 "",
233 []*http.Cookie{
234 {Name: "Cookie-1", Value: "v$1"},
235 {Name: "c2", Value: "v2"},
236 },
237 },
238 {
239 http.Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
240 "c2",
241 []*http.Cookie{
242 {Name: "c2", Value: "v2"},
243 },
244 },
245}
246
247func TestReadCookies(t *testing.T) {
248 for i, tt := range readCookiesTests {
249 for n := 0; n < 2; n++ { // to verify readCookies doesn't mutate its input
250 c := readCookies(tt.Header, tt.Filter)
251 if !reflect.DeepEqual(c, tt.Cookies) {
252 t.Errorf("#%d readCookies:\nhave: %s\nwant: %s\n", i, toJSON(c), toJSON(tt.Cookies))
253 continue
254 }
255 }
256 }
257}
258
259func TestCookieSanitizeValue(t *testing.T) {
260 defer log.SetOutput(os.Stderr)
261 var logbuf bytes.Buffer
262 log.SetOutput(&logbuf)
263
264 tests := []struct {
265 in, want string
266 }{
267 {"foo", "foo"},
268 {"foo bar", "foobar"},
269 {"\x00\x7e\x7f\x80", "\x7e"},
270 {`"withquotes"`, "withquotes"},
271 }
272 for _, tt := range tests {
273 if got := sanitizeCookieValue(tt.in); got != tt.want {
274 t.Errorf("sanitizeCookieValue(%q) = %q; want %q", tt.in, got, tt.want)
275 }
276 }
277
278 if got, sub := logbuf.String(), "dropping invalid bytes"; !strings.Contains(got, sub) {
279 t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got)
280 }
281}
282
283func TestCookieSanitizePath(t *testing.T) {
284 defer log.SetOutput(os.Stderr)
285 var logbuf bytes.Buffer
286 log.SetOutput(&logbuf)
287
288 tests := []struct {
289 in, want string
290 }{
291 {"/path", "/path"},
292 {"/path with space/", "/path with space/"},
293 {"/just;no;semicolon\x00orstuff/", "/justnosemicolonorstuff/"},
294 }
295 for _, tt := range tests {
296 if got := sanitizeCookiePath(tt.in); got != tt.want {
297 t.Errorf("sanitizeCookiePath(%q) = %q; want %q", tt.in, got, tt.want)
298 }
299 }
300
301 if got, sub := logbuf.String(), "dropping invalid bytes"; !strings.Contains(got, sub) {
302 t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got)
303 }
304}
3050
=== modified file 'http13client/request.go'
--- http13client/request.go 2014-03-19 23:13:58 +0000
+++ http13client/request.go 2014-06-20 13:02:58 +0000
@@ -69,18 +69,31 @@
6969
70// A Request represents an HTTP request received by a server70// A Request represents an HTTP request received by a server
71// or to be sent by a client.71// or to be sent by a client.
72//
73// The field semantics differ slightly between client and server
74// usage. In addition to the notes on the fields below, see the
75// documentation for Request.Write and RoundTripper.
72type Request struct {76type Request struct {
73 Method string // GET, POST, PUT, etc.77 // Method specifies the HTTP method (GET, POST, PUT, etc.).
78 // For client requests an empty string means GET.
79 Method string
7480
75 // URL is created from the URI supplied on the Request-Line81 // URL specifies either the URI being requested (for server
76 // as stored in RequestURI.82 // requests) or the URL to access (for client requests).
77 //83 //
78 // For most requests, fields other than Path and RawQuery84 // For server requests the URL is parsed from the URI
79 // will be empty. (See RFC 2616, Section 5.1.2)85 // supplied on the Request-Line as stored in RequestURI. For
86 // most requests, fields other than Path and RawQuery will be
87 // empty. (See RFC 2616, Section 5.1.2)
88 //
89 // For client requests, the URL's Host specifies the server to
90 // connect to, while the Request's Host field optionally
91 // specifies the Host header value to send in the HTTP
92 // request.
80 URL *url.URL93 URL *url.URL
8194
82 // The protocol version for incoming requests.95 // The protocol version for incoming requests.
83 // Outgoing requests always use HTTP/1.1.96 // Client requests always use HTTP/1.1.
84 Proto string // "HTTP/1.0"97 Proto string // "HTTP/1.0"
85 ProtoMajor int // 198 ProtoMajor int // 1
86 ProtoMinor int // 099 ProtoMinor int // 0
@@ -104,15 +117,20 @@
104 // The request parser implements this by canonicalizing the117 // The request parser implements this by canonicalizing the
105 // name, making the first character and any characters118 // name, making the first character and any characters
106 // following a hyphen uppercase and the rest lowercase.119 // following a hyphen uppercase and the rest lowercase.
120 //
121 // For client requests certain headers are automatically
122 // added and may override values in Header.
123 //
124 // See the documentation for the Request.Write method.
107 Header http.Header125 Header http.Header
108126
109 // Body is the request's body.127 // Body is the request's body.
110 //128 //
111 // For client requests, a nil body means the request has no129 // For client requests a nil body means the request has no
112 // body, such as a GET request. The HTTP Client's Transport130 // body, such as a GET request. The HTTP Client's Transport
113 // is responsible for calling the Close method.131 // is responsible for calling the Close method.
114 //132 //
115 // For server requests, the Request Body is always non-nil133 // For server requests the Request Body is always non-nil
116 // but will return EOF immediately when no body is present.134 // but will return EOF immediately when no body is present.
117 // The Server will close the request body. The ServeHTTP135 // The Server will close the request body. The ServeHTTP
118 // Handler does not need to.136 // Handler does not need to.
@@ -122,7 +140,7 @@
122 // The value -1 indicates that the length is unknown.140 // The value -1 indicates that the length is unknown.
123 // Values >= 0 indicate that the given number of bytes may141 // Values >= 0 indicate that the given number of bytes may
124 // be read from Body.142 // be read from Body.
125 // For outgoing requests, a value of 0 means unknown if Body is not nil.143 // For client requests, a value of 0 means unknown if Body is not nil.
126 ContentLength int64144 ContentLength int64
127145
128 // TransferEncoding lists the transfer encodings from outermost to146 // TransferEncoding lists the transfer encodings from outermost to
@@ -133,13 +151,18 @@
133 TransferEncoding []string151 TransferEncoding []string
134152
135 // Close indicates whether to close the connection after153 // Close indicates whether to close the connection after
136 // replying to this request.154 // replying to this request (for servers) or after sending
155 // the request (for clients).
137 Close bool156 Close bool
138157
139 // The host on which the URL is sought.158 // For server requests Host specifies the host on which the
140 // Per RFC 2616, this is either the value of the Host: header159 // URL is sought. Per RFC 2616, this is either the value of
141 // or the host name given in the URL itself.160 // the "Host" header or the host name given in the URL itself.
142 // It may be of the form "host:port".161 // It may be of the form "host:port".
162 //
163 // For client requests Host optionally overrides the Host
164 // header to send. If empty, the Request.Write method uses
165 // the value of URL.Host.
143 Host string166 Host string
144167
145 // Form contains the parsed form data, including both the URL168 // Form contains the parsed form data, including both the URL
@@ -159,12 +182,24 @@
159 // The HTTP client ignores MultipartForm and uses Body instead.182 // The HTTP client ignores MultipartForm and uses Body instead.
160 MultipartForm *multipart.Form183 MultipartForm *multipart.Form
161184
162 // Trailer maps trailer keys to values. Like for Header, if the185 // Trailer specifies additional headers that are sent after the request
163 // response has multiple trailer lines with the same key, they will be186 // body.
164 // concatenated, delimited by commas.187 //
165 // For server requests, Trailer is only populated after Body has been188 // For server requests the Trailer map initially contains only the
166 // closed or fully consumed.189 // trailer keys, with nil values. (The client declares which trailers it
167 // Trailer support is only partially complete.190 // will later send.) While the handler is reading from Body, it must
191 // not reference Trailer. After reading from Body returns EOF, Trailer
192 // can be read again and will contain non-nil values, if they were sent
193 // by the client.
194 //
195 // For client requests Trailer must be initialized to a map containing
196 // the trailer keys to later send. The values may be nil or their final
197 // values. The ContentLength must be 0 or -1, to send a chunked request.
198 // After the HTTP request is sent the map values can be updated while
199 // the request body is read. Once the body returns EOF, the caller must
200 // not mutate Trailer.
201 //
202 // Few HTTP clients, servers, or proxies support HTTP trailers.
168 Trailer http.Header203 Trailer http.Header
169204
170 // RemoteAddr allows HTTP servers and other software to record205 // RemoteAddr allows HTTP servers and other software to record
@@ -382,7 +417,6 @@
382 return err417 return err
383 }418 }
384419
385 // TODO: split long values? (If so, should share code with Conn.Write)
386 err = req.Header.WriteSubset(w, reqWriteExcludeHeader)420 err = req.Header.WriteSubset(w, reqWriteExcludeHeader)
387 if err != nil {421 if err != nil {
388 return err422 return err
@@ -589,32 +623,6 @@
589623
590 fixPragmaCacheControl(req.Header)624 fixPragmaCacheControl(req.Header)
591625
592 // TODO: Parse specific header values:
593 // Accept
594 // Accept-Encoding
595 // Accept-Language
596 // Authorization
597 // Cache-Control
598 // Connection
599 // Date
600 // Expect
601 // From
602 // If-Match
603 // If-Modified-Since
604 // If-None-Match
605 // If-Range
606 // If-Unmodified-Since
607 // Max-Forwards
608 // Proxy-Authorization
609 // Referer [sic]
610 // TE (transfer-codings)
611 // Trailer
612 // Transfer-Encoding
613 // Upgrade
614 // User-Agent
615 // Via
616 // Warning
617
618 err = readTransfer(req, b)626 err = readTransfer(req, b)
619 if err != nil {627 if err != nil {
620 return nil, err628 return nil, err
@@ -783,9 +791,7 @@
783 }791 }
784792
785 mr, err := r.multipartReader()793 mr, err := r.multipartReader()
786 if err == ErrNotMultipart {794 if err != nil {
787 return nil
788 } else if err != nil {
789 return err795 return err
790 }796 }
791797
@@ -863,3 +869,9 @@
863func (r *Request) wantsClose() bool {869func (r *Request) wantsClose() bool {
864 return hasToken(r.Header.Get("Connection"), "close")870 return hasToken(r.Header.Get("Connection"), "close")
865}871}
872
873func (r *Request) closeBody() {
874 if r.Body != nil {
875 r.Body.Close()
876 }
877}
866878
=== modified file 'http13client/request_test.go'
--- http13client/request_test.go 2014-03-20 09:26:28 +0000
+++ http13client/request_test.go 2014-06-20 13:02:58 +0000
@@ -155,7 +155,25 @@
155 req.Header = http.Header{"Content-Type": {"text/plain"}}155 req.Header = http.Header{"Content-Type": {"text/plain"}}
156 multipart, err = req.MultipartReader()156 multipart, err = req.MultipartReader()
157 if multipart != nil {157 if multipart != nil {
158 t.Errorf("unexpected multipart for text/plain")158 t.Error("unexpected multipart for text/plain")
159 }
160}
161
162func TestParseMultipartForm(t *testing.T) {
163 req := &Request{
164 Method: "POST",
165 Header: http.Header{"Content-Type": {`multipart/form-data; boundary="foo123"`}},
166 Body: ioutil.NopCloser(new(bytes.Buffer)),
167 }
168 err := req.ParseMultipartForm(25)
169 if err == nil {
170 t.Error("expected multipart EOF, got nil")
171 }
172
173 req.Header = http.Header{"Content-Type": {"text/plain"}}
174 err = req.ParseMultipartForm(25)
175 if err != ErrNotMultipart {
176 t.Error("expected ErrNotMultipart for text/plain")
159 }177 }
160}178}
161179
@@ -221,16 +239,38 @@
221 validateTestMultipartContents(t, req, true)239 validateTestMultipartContents(t, req, true)
222}240}
223241
224func TestEmptyMultipartRequest(t *testing.T) {242func TestMissingFileMultipartRequest(t *testing.T) {
225 // Test that FormValue and FormFile automatically invoke243 // Test that FormFile returns an error if
226 // ParseMultipartForm and return the right values.244 // the named file is missing.
227 req, err := NewRequest("GET", "/", nil)245 req := newTestMultipartRequest(t)
228 if err != nil {
229 t.Errorf("NewRequest err = %q", err)
230 }
231 testMissingFile(t, req)246 testMissingFile(t, req)
232}247}
233248
249// Test that FormValue invokes ParseMultipartForm.
250func TestFormValueCallsParseMultipartForm(t *testing.T) {
251 req, _ := NewRequest("POST", "http://www.google.com/", strings.NewReader("z=post"))
252 req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")
253 if req.Form != nil {
254 t.Fatal("Unexpected request Form, want nil")
255 }
256 req.FormValue("z")
257 if req.Form == nil {
258 t.Fatal("ParseMultipartForm not called by FormValue")
259 }
260}
261
262// Test that FormFile invokes ParseMultipartForm.
263func TestFormFileCallsParseMultipartForm(t *testing.T) {
264 req := newTestMultipartRequest(t)
265 if req.Form != nil {
266 t.Fatal("Unexpected request Form, want nil")
267 }
268 req.FormFile("")
269 if req.Form == nil {
270 t.Fatal("ParseMultipartForm not called by FormFile")
271 }
272}
273
234// Test that ParseMultipartForm errors if called274// Test that ParseMultipartForm errors if called
235// after MultipartReader on the same request.275// after MultipartReader on the same request.
236func TestParseMultipartFormOrder(t *testing.T) {276func TestParseMultipartFormOrder(t *testing.T) {
237277
=== modified file 'http13client/response.go'
--- http13client/response.go 2014-03-19 23:43:25 +0000
+++ http13client/response.go 2014-06-20 13:02:58 +0000
@@ -8,6 +8,7 @@
88
9import (9import (
10 "bufio"10 "bufio"
11 "bytes"
11 "crypto/tls"12 "crypto/tls"
12 "errors"13 "errors"
13 "io"14 "io"
@@ -47,7 +48,8 @@
47 //48 //
48 // The http Client and Transport guarantee that Body is always49 // The http Client and Transport guarantee that Body is always
49 // non-nil, even on responses without a body or responses with50 // non-nil, even on responses without a body or responses with
50 // a zero-lengthed body.51 // a zero-length body. It is the caller's responsibility to
52 // close Body.
51 //53 //
52 // The Body is automatically dechunked if the server replied54 // The Body is automatically dechunked if the server replied
53 // with a "chunked" Transfer-Encoding.55 // with a "chunked" Transfer-Encoding.
@@ -200,7 +202,6 @@
200//202//
201// Body is closed after it is sent.203// Body is closed after it is sent.
202func (r *Response) Write(w io.Writer) error {204func (r *Response) Write(w io.Writer) error {
203
204 // Status line205 // Status line
205 text := r.Status206 text := r.Status
206 if text == "" {207 if text == "" {
@@ -212,10 +213,45 @@
212 protoMajor, protoMinor := strconv.Itoa(r.ProtoMajor), strconv.Itoa(r.ProtoMinor)213 protoMajor, protoMinor := strconv.Itoa(r.ProtoMajor), strconv.Itoa(r.ProtoMinor)
213 statusCode := strconv.Itoa(r.StatusCode) + " "214 statusCode := strconv.Itoa(r.StatusCode) + " "
214 text = strings.TrimPrefix(text, statusCode)215 text = strings.TrimPrefix(text, statusCode)
215 io.WriteString(w, "HTTP/"+protoMajor+"."+protoMinor+" "+statusCode+text+"\r\n")216 if _, err := io.WriteString(w, "HTTP/"+protoMajor+"."+protoMinor+" "+statusCode+text+"\r\n"); err != nil {
217 return err
218 }
219
220 // Clone it, so we can modify r1 as needed.
221 r1 := new(Response)
222 *r1 = *r
223 if r1.ContentLength == 0 && r1.Body != nil {
224 // Is it actually 0 length? Or just unknown?
225 var buf [1]byte
226 n, err := r1.Body.Read(buf[:])
227 if err != nil && err != io.EOF {
228 return err
229 }
230 if n == 0 {
231 // Reset it to a known zero reader, in case underlying one
232 // is unhappy being read repeatedly.
233 r1.Body = eofReader
234 } else {
235 r1.ContentLength = -1
236 r1.Body = struct {
237 io.Reader
238 io.Closer
239 }{
240 io.MultiReader(bytes.NewReader(buf[:1]), r.Body),
241 r.Body,
242 }
243 }
244 }
245 // If we're sending a non-chunked HTTP/1.1 response without a
246 // content-length, the only way to do that is the old HTTP/1.0
247 // way, by noting the EOF with a connection close, so we need
248 // to set Close.
249 if r1.ContentLength == -1 && !r1.Close && r1.ProtoAtLeast(1, 1) && !chunked(r1.TransferEncoding) {
250 r1.Close = true
251 }
216252
217 // Process Body,ContentLength,Close,Trailer253 // Process Body,ContentLength,Close,Trailer
218 tw, err := newTransferWriter(r)254 tw, err := newTransferWriter(r1)
219 if err != nil {255 if err != nil {
220 return err256 return err
221 }257 }
@@ -230,8 +266,19 @@
230 return err266 return err
231 }267 }
232268
269 // contentLengthAlreadySent may have been already sent for
270 // POST/PUT requests, even if zero length. See Issue 8180.
271 contentLengthAlreadySent := tw.shouldSendContentLength()
272 if r1.ContentLength == 0 && !chunked(r1.TransferEncoding) && !contentLengthAlreadySent {
273 if _, err := io.WriteString(w, "Content-Length: 0\r\n"); err != nil {
274 return err
275 }
276 }
277
233 // End-of-header278 // End-of-header
234 io.WriteString(w, "\r\n")279 if _, err := io.WriteString(w, "\r\n"); err != nil {
280 return err
281 }
235282
236 // Write body and trailer283 // Write body and trailer
237 err = tw.WriteBody(w)284 err = tw.WriteBody(w)
238285
=== modified file 'http13client/response_test.go'
--- http13client/response_test.go 2014-03-19 23:13:58 +0000
+++ http13client/response_test.go 2014-06-20 13:02:58 +0000
@@ -30,6 +30,10 @@
30 return &Request{Method: method}30 return &Request{Method: method}
31}31}
3232
33func dummyReq11(method string) *Request {
34 return &Request{Method: method, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1}
35}
36
33var respTests = []respTest{37var respTests = []respTest{
34 // Unchunked response without Content-Length.38 // Unchunked response without Content-Length.
35 {39 {
3640
=== modified file 'http13client/responsewrite_test.go'
--- http13client/responsewrite_test.go 2014-03-19 23:13:58 +0000
+++ http13client/responsewrite_test.go 2014-06-20 13:02:58 +0000
@@ -27,7 +27,7 @@
27 ProtoMinor: 0,27 ProtoMinor: 0,
28 Request: dummyReq("GET"),28 Request: dummyReq("GET"),
29 Header: http.Header{},29 Header: http.Header{},
30 Body: ioutil.NopCloser(bytes.NewBufferString("abcdef")),30 Body: ioutil.NopCloser(strings.NewReader("abcdef")),
31 ContentLength: 6,31 ContentLength: 6,
32 },32 },
3333
@@ -50,6 +50,106 @@
50 "\r\n" +50 "\r\n" +
51 "abcdef",51 "abcdef",
52 },52 },
53 // HTTP/1.1 response with unknown length and Connection: close
54 {
55 Response{
56 StatusCode: 200,
57 ProtoMajor: 1,
58 ProtoMinor: 1,
59 Request: dummyReq("GET"),
60 Header: http.Header{},
61 Body: ioutil.NopCloser(strings.NewReader("abcdef")),
62 ContentLength: -1,
63 Close: true,
64 },
65 "HTTP/1.1 200 OK\r\n" +
66 "Connection: close\r\n" +
67 "\r\n" +
68 "abcdef",
69 },
70 // HTTP/1.1 response with unknown length and not setting connection: close
71 {
72 Response{
73 StatusCode: 200,
74 ProtoMajor: 1,
75 ProtoMinor: 1,
76 Request: dummyReq11("GET"),
77 Header: http.Header{},
78 Body: ioutil.NopCloser(strings.NewReader("abcdef")),
79 ContentLength: -1,
80 Close: false,
81 },
82 "HTTP/1.1 200 OK\r\n" +
83 "Connection: close\r\n" +
84 "\r\n" +
85 "abcdef",
86 },
87 // HTTP/1.1 response with unknown length and not setting connection: close, but
88 // setting chunked.
89 {
90 Response{
91 StatusCode: 200,
92 ProtoMajor: 1,
93 ProtoMinor: 1,
94 Request: dummyReq11("GET"),
95 Header: http.Header{},
96 Body: ioutil.NopCloser(strings.NewReader("abcdef")),
97 ContentLength: -1,
98 TransferEncoding: []string{"chunked"},
99 Close: false,
100 },
101 "HTTP/1.1 200 OK\r\n" +
102 "Transfer-Encoding: chunked\r\n\r\n" +
103 "6\r\nabcdef\r\n0\r\n\r\n",
104 },
105 // HTTP/1.1 response 0 content-length, and nil body
106 {
107 Response{
108 StatusCode: 200,
109 ProtoMajor: 1,
110 ProtoMinor: 1,
111 Request: dummyReq11("GET"),
112 Header: http.Header{},
113 Body: nil,
114 ContentLength: 0,
115 Close: false,
116 },
117 "HTTP/1.1 200 OK\r\n" +
118 "Content-Length: 0\r\n" +
119 "\r\n",
120 },
121 // HTTP/1.1 response 0 content-length, and non-nil empty body
122 {
123 Response{
124 StatusCode: 200,
125 ProtoMajor: 1,
126 ProtoMinor: 1,
127 Request: dummyReq11("GET"),
128 Header: http.Header{},
129 Body: ioutil.NopCloser(strings.NewReader("")),
130 ContentLength: 0,
131 Close: false,
132 },
133 "HTTP/1.1 200 OK\r\n" +
134 "Content-Length: 0\r\n" +
135 "\r\n",
136 },
137 // HTTP/1.1 response 0 content-length, and non-nil non-empty body
138 {
139 Response{
140 StatusCode: 200,
141 ProtoMajor: 1,
142 ProtoMinor: 1,
143 Request: dummyReq11("GET"),
144 Header: http.Header{},
145 Body: ioutil.NopCloser(strings.NewReader("foo")),
146 ContentLength: 0,
147 Close: false,
148 },
149 "HTTP/1.1 200 OK\r\n" +
150 "Connection: close\r\n" +
151 "\r\nfoo",
152 },
53 // HTTP/1.1, chunked coding; empty trailer; close153 // HTTP/1.1, chunked coding; empty trailer; close
54 {154 {
55 Response{155 Response{
@@ -92,6 +192,22 @@
92 "Foo: Bar Baz\r\n" +192 "Foo: Bar Baz\r\n" +
93 "\r\n",193 "\r\n",
94 },194 },
195
196 // Want a single Content-Length header. Fixing issue 8180 where
197 // there were two.
198 {
199 Response{
200 StatusCode: http.StatusOK,
201 ProtoMajor: 1,
202 ProtoMinor: 1,
203 Request: &Request{Method: "POST"},
204 Header: http.Header{},
205 ContentLength: 0,
206 TransferEncoding: nil,
207 Body: nil,
208 },
209 "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
210 },
95 }211 }
96212
97 for i := range respWriteTests {213 for i := range respWriteTests {
98214
=== modified file 'http13client/server.go'
--- http13client/server.go 2014-03-19 23:13:58 +0000
+++ http13client/server.go 2014-06-20 13:02:58 +0000
@@ -10,21 +10,27 @@
10 "io/ioutil"10 "io/ioutil"
11 "log"11 "log"
12 "net"12 "net"
13 "strings"
14 "sync"13 "sync"
15)14)
1615
16type eofReaderWithWriteTo struct{}
17
18func (eofReaderWithWriteTo) WriteTo(io.Writer) (int64, error) { return 0, nil }
19func (eofReaderWithWriteTo) Read([]byte) (int, error) { return 0, io.EOF }
20
17// eofReader is a non-nil io.ReadCloser that always returns EOF.21// eofReader is a non-nil io.ReadCloser that always returns EOF.
18// It embeds a *strings.Reader so it still has a WriteTo method22// It has a WriteTo method so io.Copy won't need a buffer.
19// and io.Copy won't need a buffer.
20var eofReader = &struct {23var eofReader = &struct {
21 *strings.Reader24 eofReaderWithWriteTo
22 io.Closer25 io.Closer
23}{26}{
24 strings.NewReader(""),27 eofReaderWithWriteTo{},
25 ioutil.NopCloser(nil),28 ioutil.NopCloser(nil),
26}29}
2730
31// Verify that an io.Copy from an eofReader won't require a buffer.
32var _ io.WriterTo = eofReader
33
28// loggingConn is used for debugging.34// loggingConn is used for debugging.
29type loggingConn struct {35type loggingConn struct {
30 name string36 name string
3137
=== modified file 'http13client/transfer.go'
--- http13client/transfer.go 2014-03-19 23:13:58 +0000
+++ http13client/transfer.go 2014-06-20 13:02:58 +0000
@@ -13,6 +13,7 @@
13 "io/ioutil"13 "io/ioutil"
14 "net/http"14 "net/http"
15 "net/textproto"15 "net/textproto"
16 "sort"
16 "strconv"17 "strconv"
17 "strings"18 "strings"
18 "sync"19 "sync"
@@ -144,11 +145,10 @@
144 return false145 return false
145}146}
146147
147func (t *transferWriter) WriteHeader(w io.Writer) (err error) {148func (t *transferWriter) WriteHeader(w io.Writer) error {
148 if t.Close {149 if t.Close {
149 _, err = io.WriteString(w, "Connection: close\r\n")150 if _, err := io.WriteString(w, "Connection: close\r\n"); err != nil {
150 if err != nil {151 return err
151 return
152 }152 }
153 }153 }
154154
@@ -156,43 +156,44 @@
156 // function of the sanitized field triple (Body, ContentLength,156 // function of the sanitized field triple (Body, ContentLength,
157 // TransferEncoding)157 // TransferEncoding)
158 if t.shouldSendContentLength() {158 if t.shouldSendContentLength() {
159 io.WriteString(w, "Content-Length: ")159 if _, err := io.WriteString(w, "Content-Length: "); err != nil {
160 _, err = io.WriteString(w, strconv.FormatInt(t.ContentLength, 10)+"\r\n")160 return err
161 if err != nil {161 }
162 return162 if _, err := io.WriteString(w, strconv.FormatInt(t.ContentLength, 10)+"\r\n"); err != nil {
163 return err
163 }164 }
164 } else if chunked(t.TransferEncoding) {165 } else if chunked(t.TransferEncoding) {
165 _, err = io.WriteString(w, "Transfer-Encoding: chunked\r\n")166 if _, err := io.WriteString(w, "Transfer-Encoding: chunked\r\n"); err != nil {
166 if err != nil {167 return err
167 return
168 }168 }
169 }169 }
170170
171 // Write Trailer header171 // Write Trailer header
172 if t.Trailer != nil {172 if t.Trailer != nil {
173 // TODO: At some point, there should be a generic mechanism for173 keys := make([]string, 0, len(t.Trailer))
174 // writing long headers, using HTTP line splitting
175 io.WriteString(w, "Trailer: ")
176 needComma := false
177 for k := range t.Trailer {174 for k := range t.Trailer {
178 k = http.CanonicalHeaderKey(k)175 k = http.CanonicalHeaderKey(k)
179 switch k {176 switch k {
180 case "Transfer-Encoding", "Trailer", "Content-Length":177 case "Transfer-Encoding", "Trailer", "Content-Length":
181 return &badStringError{"invalid Trailer key", k}178 return &badStringError{"invalid Trailer key", k}
182 }179 }
183 if needComma {180 keys = append(keys, k)
184 io.WriteString(w, ",")181 }
182 if len(keys) > 0 {
183 sort.Strings(keys)
184 // TODO: could do better allocation-wise here, but trailers are rare,
185 // so being lazy for now.
186 if _, err := io.WriteString(w, "Trailer: "+strings.Join(keys, ",")+"\r\n"); err != nil {
187 return err
185 }188 }
186 io.WriteString(w, k)
187 needComma = true
188 }189 }
189 _, err = io.WriteString(w, "\r\n")
190 }190 }
191191
192 return192 return nil
193}193}
194194
195func (t *transferWriter) WriteBody(w io.Writer) (err error) {195func (t *transferWriter) WriteBody(w io.Writer) error {
196 var err error
196 var ncopy int64197 var ncopy int64
197198
198 // Write body199 // Write body
@@ -229,11 +230,16 @@
229230
230 // TODO(petar): Place trailer writer code here.231 // TODO(petar): Place trailer writer code here.
231 if chunked(t.TransferEncoding) {232 if chunked(t.TransferEncoding) {
233 // Write Trailer header
234 if t.Trailer != nil {
235 if err := t.Trailer.Write(w); err != nil {
236 return err
237 }
238 }
232 // Last chunk, empty trailer239 // Last chunk, empty trailer
233 _, err = io.WriteString(w, "\r\n")240 _, err = io.WriteString(w, "\r\n")
234 }241 }
235242 return err
236 return
237}243}
238244
239type transferReader struct {245type transferReader struct {
@@ -265,6 +271,22 @@
265 return true271 return true
266}272}
267273
274var (
275 suppressedHeaders304 = []string{"Content-Type", "Content-Length", "Transfer-Encoding"}
276 suppressedHeadersNoBody = []string{"Content-Length", "Transfer-Encoding"}
277)
278
279func suppressedHeaders(status int) []string {
280 switch {
281 case status == 304:
282 // RFC 2616 section 10.3.5: "the response MUST NOT include other entity-headers"
283 return suppressedHeaders304
284 case !bodyAllowedForStatus(status):
285 return suppressedHeadersNoBody
286 }
287 return nil
288}
289
268// msg is *Request or *Response.290// msg is *Request or *Response.
269func readTransfer(msg interface{}, r *bufio.Reader) (err error) {291func readTransfer(msg interface{}, r *bufio.Reader) (err error) {
270 t := &transferReader{RequestMethod: "GET"}292 t := &transferReader{RequestMethod: "GET"}
@@ -511,7 +533,7 @@
511 case "Transfer-Encoding", "Trailer", "Content-Length":533 case "Transfer-Encoding", "Trailer", "Content-Length":
512 return nil, &badStringError{"bad trailer key", key}534 return nil, &badStringError{"bad trailer key", key}
513 }535 }
514 trailer.Del(key)536 trailer[key] = nil
515 }537 }
516 if len(trailer) == 0 {538 if len(trailer) == 0 {
517 return nil, nil539 return nil, nil
@@ -643,13 +665,23 @@
643 }665 }
644 switch rr := b.hdr.(type) {666 switch rr := b.hdr.(type) {
645 case *Request:667 case *Request:
646 rr.Trailer = http.Header(hdr)668 mergeSetHeader(&rr.Trailer, http.Header(hdr))
647 case *Response:669 case *Response:
648 rr.Trailer = http.Header(hdr)670 mergeSetHeader(&rr.Trailer, http.Header(hdr))
649 }671 }
650 return nil672 return nil
651}673}
652674
675func mergeSetHeader(dst *http.Header, src http.Header) {
676 if *dst == nil {
677 *dst = src
678 return
679 }
680 for k, vv := range src {
681 (*dst)[k] = vv
682 }
683}
684
653func (b *body) Close() error {685func (b *body) Close() error {
654 b.mu.Lock()686 b.mu.Lock()
655 defer b.mu.Unlock()687 defer b.mu.Unlock()
656688
=== modified file 'http13client/transport.go'
--- http13client/transport.go 2014-03-20 09:26:28 +0000
+++ http13client/transport.go 2014-06-20 13:02:58 +0000
@@ -110,6 +110,9 @@
110// An error is returned if the proxy environment is invalid.110// An error is returned if the proxy environment is invalid.
111// A nil URL and nil error are returned if no proxy is defined in the111// A nil URL and nil error are returned if no proxy is defined in the
112// environment, or a proxy should not be used for the given request.112// environment, or a proxy should not be used for the given request.
113//
114// As a special case, if req.URL.Host is "localhost" (with or without
115// a port number), then a nil URL and nil error will be returned.
113func ProxyFromEnvironment(req *Request) (*url.URL, error) {116func ProxyFromEnvironment(req *Request) (*url.URL, error) {
114 proxy := httpProxyEnv.Get()117 proxy := httpProxyEnv.Get()
115 if proxy == "" {118 if proxy == "" {
@@ -161,9 +164,11 @@
161// and redirects), see Get, Post, and the Client type.164// and redirects), see Get, Post, and the Client type.
162func (t *Transport) RoundTrip(req *Request) (resp *Response, err error) {165func (t *Transport) RoundTrip(req *Request) (resp *Response, err error) {
163 if req.URL == nil {166 if req.URL == nil {
167 req.closeBody()
164 return nil, errors.New("http: nil Request.URL")168 return nil, errors.New("http: nil Request.URL")
165 }169 }
166 if req.Header == nil {170 if req.Header == nil {
171 req.closeBody()
167 return nil, errors.New("http: nil Request.Header")172 return nil, errors.New("http: nil Request.Header")
168 }173 }
169 if req.URL.Scheme != "http" && req.URL.Scheme != "https" {174 if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
@@ -174,16 +179,19 @@
174 }179 }
175 t.altMu.RUnlock()180 t.altMu.RUnlock()
176 if rt == nil {181 if rt == nil {
182 req.closeBody()
177 return nil, &badStringError{"unsupported protocol scheme", req.URL.Scheme}183 return nil, &badStringError{"unsupported protocol scheme", req.URL.Scheme}
178 }184 }
179 return rt.RoundTrip(req)185 return rt.RoundTrip(req)
180 }186 }
181 if req.URL.Host == "" {187 if req.URL.Host == "" {
188 req.closeBody()
182 return nil, errors.New("http: no Host in request URL")189 return nil, errors.New("http: no Host in request URL")
183 }190 }
184 treq := &transportRequest{Request: req}191 treq := &transportRequest{Request: req}
185 cm, err := t.connectMethodForRequest(treq)192 cm, err := t.connectMethodForRequest(treq)
186 if err != nil {193 if err != nil {
194 req.closeBody()
187 return nil, err195 return nil, err
188 }196 }
189197
@@ -194,6 +202,7 @@
194 pconn, err := t.getConn(req, cm)202 pconn, err := t.getConn(req, cm)
195 if err != nil {203 if err != nil {
196 t.setReqCanceler(req, nil)204 t.setReqCanceler(req, nil)
205 req.closeBody()
197 return nil, err206 return nil, err
198 }207 }
199208
@@ -231,9 +240,6 @@
231 t.idleConn = nil240 t.idleConn = nil
232 t.idleConnCh = nil241 t.idleConnCh = nil
233 t.idleMu.Unlock()242 t.idleMu.Unlock()
234 if m == nil {
235 return
236 }
237 for _, conns := range m {243 for _, conns := range m {
238 for _, pconn := range conns {244 for _, pconn := range conns {
239 pconn.close()245 pconn.close()
@@ -499,12 +505,13 @@
499 pa := cm.proxyAuth()505 pa := cm.proxyAuth()
500506
501 pconn := &persistConn{507 pconn := &persistConn{
502 t: t,508 t: t,
503 cacheKey: cm.key(),509 cacheKey: cm.key(),
504 conn: conn,510 conn: conn,
505 reqch: make(chan requestAndChan, 50),511 reqch: make(chan requestAndChan, 1),
506 writech: make(chan writeRequest, 50),512 writech: make(chan writeRequest, 1),
507 closech: make(chan struct{}),513 closech: make(chan struct{}),
514 writeErrCh: make(chan error, 1),
508 }515 }
509516
510 switch {517 switch {
@@ -589,7 +596,7 @@
589 pconn.conn = tlsConn596 pconn.conn = tlsConn
590 }597 }
591598
592 pconn.br = bufio.NewReader(pconn.conn)599 pconn.br = bufio.NewReader(noteEOFReader{pconn.conn, &pconn.sawEOF})
593 pconn.bw = bufio.NewWriter(pconn.conn)600 pconn.bw = bufio.NewWriter(pconn.conn)
594 go pconn.readLoop()601 go pconn.readLoop()
595 go pconn.writeLoop()602 go pconn.writeLoop()
@@ -722,16 +729,22 @@
722 cacheKey connectMethodKey729 cacheKey connectMethodKey
723 conn net.Conn730 conn net.Conn
724 tlsState *tls.ConnectionState731 tlsState *tls.ConnectionState
725 closed bool // whether conn has been closed
726 br *bufio.Reader // from conn732 br *bufio.Reader // from conn
733 sawEOF bool // whether we've seen EOF from conn; owned by readLoop
727 bw *bufio.Writer // to conn734 bw *bufio.Writer // to conn
728 reqch chan requestAndChan // written by roundTrip; read by readLoop735 reqch chan requestAndChan // written by roundTrip; read by readLoop
729 writech chan writeRequest // written by roundTrip; read by writeLoop736 writech chan writeRequest // written by roundTrip; read by writeLoop
730 closech chan struct{} // broadcast close when readLoop (TCP connection) closes737 closech chan struct{} // closed when conn closed
731 isProxy bool738 isProxy bool
739 // writeErrCh passes the request write error (usually nil)
740 // from the writeLoop goroutine to the readLoop which passes
741 // it off to the res.Body reader, which then uses it to decide
742 // whether or not a connection can be reused. Issue 7569.
743 writeErrCh chan error
732744
733 lk sync.Mutex // guards following 3 fields745 lk sync.Mutex // guards following fields
734 numExpectedResponses int746 numExpectedResponses int
747 closed bool // whether conn has been closed
735 broken bool // an error has happened on this connection; marked broken so it's not reused.748 broken bool // an error has happened on this connection; marked broken so it's not reused.
736 // mutateHeaderFunc is an optional func to modify extra749 // mutateHeaderFunc is an optional func to modify extra
737 // headers on each outbound request before it's written. (the750 // headers on each outbound request before it's written. (the
@@ -739,6 +752,7 @@
739 mutateHeaderFunc func(http.Header)752 mutateHeaderFunc func(http.Header)
740}753}
741754
755// isBroken reports whether this connection is in a known broken state.
742func (pc *persistConn) isBroken() bool {756func (pc *persistConn) isBroken() bool {
743 pc.lk.Lock()757 pc.lk.Lock()
744 b := pc.broken758 b := pc.broken
@@ -763,7 +777,6 @@
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches