implement sending of the request body

This commit is contained in:
Marten Seemann
2017-01-05 14:32:51 +07:00
parent cc97a2fba4
commit e3b34f2120
3 changed files with 138 additions and 15 deletions

View File

@@ -3,6 +3,7 @@ package h2quic
import (
"errors"
"fmt"
"io"
"net"
"net/http"
"strings"
@@ -186,18 +187,39 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) {
return nil, err
}
var res *http.Response
select {
case res = <-hdrChan:
c.mutex.Lock()
delete(c.responses, dataStreamID)
c.mutex.Unlock()
resc := make(chan error, 1)
if hasBody {
go func() {
resc <- c.writeRequestBody(dataStream, req.Body)
}()
}
// if an error occured on the header stream
if res == nil {
c.Close(c.headerErr)
return nil, c.headerErr
var res *http.Response
var receivedResponse bool
var bodySent bool
if !hasBody {
bodySent = true
}
for !(bodySent && receivedResponse) {
select {
case res = <-hdrChan:
receivedResponse = true
c.mutex.Lock()
delete(c.responses, dataStreamID)
c.mutex.Unlock()
if res == nil { // an error occured on the header stream
c.Close(c.headerErr)
return nil, c.headerErr
}
case err := <-resc:
bodySent = true
if err != nil {
return nil, err
}
}
}
// TODO: correctly set this variable
@@ -205,7 +227,6 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) {
isHead := (req.Method == "HEAD")
res = setLength(res, isHead, streamEnded)
utils.Debugf("%#v", res)
if streamEnded || isHead {
res.Body = noBody
@@ -225,6 +246,23 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) {
return res, nil
}
func (c *Client) writeRequestBody(dataStream utils.Stream, body io.ReadCloser) (err error) {
defer func() {
cerr := body.Close()
if err == nil {
// TODO: what to do with dataStream here? Maybe reset it?
err = cerr
}
}()
_, err = io.Copy(dataStream, body)
if err != nil {
// TODO: what to do with dataStream here? Maybe reset it?
return err
}
return dataStream.Close()
}
// Close closes the client
func (c *Client) Close(e error) {
_ = c.client.Close(e)

View File

@@ -3,6 +3,7 @@ package h2quic
import (
"bytes"
"compress/gzip"
"errors"
"net/http"
"golang.org/x/net/http2"
@@ -219,6 +220,75 @@ var _ = Describe("Client", func() {
Expect(mhf.HeadersFrame.StreamEnded()).To(BeFalse())
})
Context("requests containing a Body", func() {
var requestBody []byte
var response *http.Response
BeforeEach(func() {
requestBody = []byte("request body")
body := &mockBody{}
body.SetData(requestBody)
request.Body = body
response = &http.Response{
StatusCode: 200,
Header: http.Header{"Content-Length": []string{"1000"}},
}
})
It("sends a request", func() {
var doRsp *http.Response
var doErr error
var doReturned bool
go func() {
doRsp, doErr = client.Do(request)
doReturned = true
}()
Eventually(func() chan *http.Response { return client.responses[5] }).ShouldNot(BeNil())
client.responses[5] <- response
dataStream := qClient.streams[5]
Eventually(func() bool { return doReturned }).Should(BeTrue())
Expect(dataStream.dataWritten.Bytes()).To(Equal(requestBody))
Expect(dataStream.closed).To(BeTrue())
Expect(request.Body.(*mockBody).closed).To(BeTrue())
Expect(doErr).ToNot(HaveOccurred())
Expect(doRsp).To(Equal(response))
})
It("returns the error that occurred when reading the body", func() {
testErr := errors.New("testErr")
request.Body.(*mockBody).readErr = testErr
var doRsp *http.Response
var doErr error
var doReturned bool
go func() {
doRsp, doErr = client.Do(request)
doReturned = true
}()
Eventually(func() bool { return doReturned }).Should(BeTrue())
Expect(doErr).To(MatchError(testErr))
Expect(doRsp).To(BeNil())
Expect(request.Body.(*mockBody).closed).To(BeTrue())
})
It("returns the error that occurred when closing the body", func() {
testErr := errors.New("testErr")
request.Body.(*mockBody).closeErr = testErr
var doRsp *http.Response
var doErr error
var doReturned bool
go func() {
doRsp, doErr = client.Do(request)
doReturned = true
}()
Eventually(func() bool { return doReturned }).Should(BeTrue())
Expect(doErr).To(MatchError(testErr))
Expect(doRsp).To(BeNil())
Expect(request.Body.(*mockBody).closed).To(BeTrue())
})
})
Context("gzip compression", func() {
var gzippedData []byte // a gzipped foobar
var response *http.Response

View File

@@ -1,6 +1,8 @@
package h2quic
import (
"bytes"
"io"
"net/http"
. "github.com/onsi/ginkgo"
@@ -14,18 +16,31 @@ func (m *mockQuicRoundTripper) Do(req *http.Request) (*http.Response, error) {
}
type mockBody struct {
closed bool
reader bytes.Reader
readErr error
closeErr error
closed bool
}
func (m *mockBody) Read([]byte) (int, error) {
panic("not implemented")
func (m *mockBody) Read(p []byte) (int, error) {
if m.readErr != nil {
return 0, m.readErr
}
return m.reader.Read(p)
}
func (m *mockBody) SetData(data []byte) {
m.reader = *bytes.NewReader(data)
}
func (m *mockBody) Close() error {
m.closed = true
return nil
return m.closeErr
}
// make sure the mockBody can be used as a http.Request.Body
var _ io.ReadCloser = &mockBody{}
var _ = Describe("RoundTripper", func() {
var (
rt *QuicRoundTripper