When should an http.RoundTripper close its connection? - http

I'm using httputil.ReverseProxy with an http.RoundTripper of my own implementation that uses an ssh.Channel as a transport. My RoundTrip method looks approximately like this:
func (c SSHConnection) RoundTrip(req *http.Request) (*http.Response, error) {
ch, err := c.GetChannel()
if err != nil {
return nil, errors.New("couldn't open forwarded-tcpip channel: " + err.Error())
}
// defer ch.Close()
err = req.Write(ch)
if err != nil {
return nil, errors.New("couldn't send request: " + err.Error())
}
return http.ReadResponse(bufio.NewReader(ch), req)
}
func (c SSHConnection) GetChannel() (ssh.Channel, error) {
ch, req, err := c.Conn.OpenChannel("forwarded-tcpip", msg)
if err != nil {
return nil, err
}
go ssh.DiscardRequests(req)
return ch, nil
}
Notice the commented-out defer ch.Close(). Initially I naively closed the connection here, but the response body would sometimes be empty, due to a race between the HTTP proxy's reading of the body and this closing of the SSH channel.
Assuming, for now, that I don't care to do keep-alive, when can I close the ssh.Channel? If I don't, every request starts a new goroutine (because of go ssh.DiscardRequests(req)), so I leak a goroutine on every HTTP requests until the underlying SSH connection is closed.

An http.RoundTripper should not close the connection until after the response body has been fully consumed, or at the request of the server.
The simplest option is to fully buffer the response and close the connection immediately. In some cases this may actually be the most efficient, if the traffic mostly consists of small, independent requests.
The next option is to hook the closing of the response body to close the channel.
type Body struct {
io.ReadCloser
channel ssh.Channel
}
func (b *Body) Close() error {
b.channel.Close()
return b.ReadCloser.Close()
}
func (c SSHConnection) RoundTrip(req *http.Request) (*http.Response, error) {
ch, err := c.GetChannel()
if err != nil {
return nil, errors.New("couldn't open forwarded-tcpip channel: " + err.Error())
}
err = req.Write(ch)
if err != nil {
return nil, errors.New("couldn't send request: " + err.Error())
}
resp, err := http.ReadResponse(bufio.NewReader(ch), req)
if err != nil {
ch.Close()
return nil, err
}
resp.Body = &Body{
ReadCloser: resp.Body,
channel: ch,
}
return resp, err
}
Lastly, to make the most efficient use of the ssh channels, you could use an existing Transport with a net.Dialer which makes the ssh connection, and wraps the channel in a net.Conn interface.

Related

Sending data in Chunks using single HTTP Post connection

I receive the contents of a file from a data source in chunks. As and when I receive the chunk I want to send the chunk data to a service using http POST request. And by keeping alive the same http POST connection used for sending the first chunk I want to send the remaining chunks of data.
I came up with the following code snippet to implement something similar.
Server-Side
func handle(w http.ResponseWriter, req *http.Request) {
buf := make([]byte, 256)
var n int
for {
n, err := req.Body.Read(buf)
if n == 0 && err == io.EOF {
break
}
fmt.Printf(string(buf[:n]))
}
fmt.Printf(string(buf[:n]))
fmt.Printf("Transfer Complete")
}
Client-Side
type alphaReader struct {
reader io.Reader
}
func newAlphaReader(reader io.Reader) *alphaReader {
return &alphaReader{reader: reader}
}
func (a *alphaReader) Read(p []byte) (int, error) {
n, err := a.reader.Read(p)
return n, err
}
func (a *alphaReader) Reset(str string) {
a.reader = strings.NewReader(str)
}
func (a *alphaReader) Close() error {
return nil
}
func main() {
tr := http.DefaultTransport
alphareader := newAlphaReader(strings.NewReader("First Chunk"))
client := &http.Client{
Transport: tr,
Timeout: 0,
}
req := &http.Request{
Method: "POST",
URL: &url.URL{
Scheme: "http",
Host: "localhost:8080",
Path: "/upload",
},
ProtoMajor: 1,
ProtoMinor: 1,
ContentLength: -1,
Body: alphareader,
}
fmt.Printf("Doing request\n")
_, err := client.Do(req)
alphareader.Reset("Second Chunk")
fmt.Printf("Done request. Err: %v\n", err)
}
Here I want that when I do alphareader.Reset("Second Chunk"), the string "Second Chunk" should be sent using the POST connection made earlier. But that is not happening. The connection gets closed after sending the First Chunk of data. Also I have not written the Close() method properly which I'm not sure how to implement.
I'm newbie to golang and any suggestions would be greatly helpful regarding the same.
A *strings.Reader returns io.EOF after the initial string has been read and your wrapper does nothing to change that, so it cannot be reused. You're looking for io.Pipe to turn the request body into an io.Writer.
package main
import (
"io"
"net/http"
)
func main() {
pr, pw := io.Pipe()
req, err := http.NewRequest("POST", "http://localhost:8080/upload", pr)
if err != nil {
// TODO: handle error
}
go func() {
defer pw.Close()
if _, err := io.WriteString(pw, "first chunk"); err != nil {
_ = err // TODO: handle error
}
if _, err := io.WriteString(pw, "second chunk"); err != nil {
_ = err // TODO: handle error
}
}()
res, err := http.DefaultClient.Do(req)
if err != nil {
// TODO: handle error
}
res.Body.Close()
}
Also, don't initialize the request using a struct literal. Use one of the constructors instead. In your code you're not setting the Host and Header fields, for instance.

HTTP client returns random errors on timeout

I have an HTTP client with a custom RoundTripper which in turn uses the http.DefaultTransport to handle the request.
Now imagine I have a slow server which takes a long time to respond and it makes my http client timeout and cancel the client. Here is the code for the client:
package main
import (
"fmt"
"io/ioutil"
"net/http"
"time"
)
type rt struct {
roundTripper func(req *http.Request) (*http.Response, error)
}
func (r rt) RoundTrip(req *http.Request) (*http.Response, error) {
return r.roundTripper(req)
}
func main() {
c := http.Client{
Timeout: 3 * time.Second,
Transport: rt{RoundTripper(http.DefaultTransport)},
}
resp, err := c.Get("http://127.0.0.1:9000")
if err != nil {
fmt.Println("err:", err)
} else {
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
fmt.Println(string(body), err)
}
}
func RoundTripper(next http.RoundTripper) func(req *http.Request) (*http.Response, error) {
return func(req *http.Request) (*http.Response, error) {
resp, err := next.RoundTrip(req)
if err != nil {
return nil, fmt.Errorf("err: %w", err)
}
return resp, nil
}
}
The problem here is that the error I'm receiving on timeout is randomly one of net/http: request canceled or context deadline exceeded.
Now I know they should be semantically the same thing but I'm failing to understand why it's returning each and when?
Here is the server code if you want to try it for yourself.
The function net/http/client.setRequestCancel() is used to set the cancel of the request. There are three ways
The second will return: net/http: request canceled
The third will return: context deadline exceeded
Because both use the same deadline, time.now()+client.Timeout.
So according to the runtime schedule, the request will be cancelled randomly through these two methods.
https://github.com/golang/go/blob/master/src/net/http/transport.go#L2652
case <-cancelChan:
// return err: net/http: request
pc.t.CancelRequest(req.Request) canceled
cancelChan = nil
case <-ctxDoneChan:
// return err:
pc.t.cancelRequest(req.Request, req.Context().Err())
cancelChan = nil
ctxDoneChan = nil

Is there a way to use a specific TCPConn to make HTTP requests?

I am trying to set up a way to communicate between a central server running Go an a fleet of IoT devices (also running Go).
For each device, it connects to to the central server through a persistent TCPConn. These devices are behind a router(s). The central server saves that connection and sends/receives messages through it. Right now, this is fully functional and works.
However, now the message passing is getting complicated enough that the utility provided by HTTP rather than pure TCP is becoming necessary.
I have attempted to write a version of http.Transport that returns said connection. However, I am unable to provide and return a valid connection from the the Dial/DialContext functions.
IoT Device
func main() {
http.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})
tcpAddr, err := net.ResolveTCPAddr("tcp", "###.###.###.###:8533")
if err != nil {
panic(err)
}
conn, err := net.DialTCP("tcp", nil, tcpAddr)
if err != nil {
panic(err)
}
err = conn.SetKeepAlive(true)
if err != nil {
panic(err)
}
err = conn.SetKeepAlivePeriod(time.Second * 10)
if err != nil {
panic(err)
}
fmt.Println("Listening")
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}
Central Server
func main() {
tcpAddr, err := net.ResolveTCPAddr("tcp", port)
if err != nil {
panic(err)
}
listener, err := net.ListenTCP("tcp", tcpAddr)
if err != nil {
panic(err)
}
conn, err := listener.AcceptTCP()
if err != nil {
panic(err)
}
fmt.Println("Received conn, attempting to send HTTP through connection")
dialFunc := func(network, addr string) (net.Conn, error) {
return conn, nil
}
t := http.Transport{
Dial: dialFunc,
}
client := http.Client{
Transport: &t,
}
fmt.Println("Making request")
res, err := client.Get("http://www.shouldNotMatter.com:8080/foo") // HANGS HERE
if err != nil {
fmt.Println(err)
}
fmt.Println("Received response")
defer res.Body.Close()
if res.StatusCode == http.StatusOK {
bodyBytes, err := ioutil.ReadAll(res.Body)
if err != nil {
panic(err)
}
bodyString := string(bodyBytes)
fmt.Println(bodyString)
} else {
fmt.Println(res)
}
}
Upon using a debugger to see where it hangs, it seems that it gets stuck in a select statement during the pconn roundtrip. Line 2420 in https://golang.org/src/net/http/transport.go?s=3397:10477
Create a type that returns an existing connection from a dial method:
type connDialer struct {
c net.Conn
}
func (cd connDialer) Dial(network, addr string) (net.Conn, error) {
return cd.c, nil
}
Use the dial method value in a transport:
client := http.Client{Transport: &http.Transport{Dial: connDialer{c}.Dial}}
where c is the existing net.Conn.
Try it on the playground (it works for at one request. It will fail when the client dials a second connection).
The overall approach is fragile. Consider using WebSockets, gRPC or other protocols that are designed to support bi-directional communication.
You missed the code on the client code. The client makes an idle connection to the server and does nothing with it so the connection will definitely hang up. You need to pass the connection to the HTTP server. This can be achieved by using net/http.Serve and passing a net.Listener to it.
type connListener struct {
conn net.Conn
ch chan struct{}
}
func (c connListener) Accept() (Conn, error) {
if c.conn != nil {
conn := c.conn
c.conn = nil
return conn, nil
}
<-c.ch
return nil, errors.New("listener closed")
}
func (c connListener) Close() error {
close(c.ch)
return nil
}
func (c connListener) Addr() net.Addr {
return c.conn.LocalAddr()
}
// call it like this
http.Serve(connListener{conn, make(chan struct{})}, nil)
BTW are you having the client connect to the server and then reverse the connection, making the client behave like an HTTP server and the server behave like the HTTP client? You might want to google "reverse http" for some information on this.

Use same headers for every request

I want to be able to use the same headers for every request made by an HTTP client without having to specify them for every request like so:
req, err := http.NewRequest("GET", fmt.Sprintf("https://%s", endpoint), nil)
if err != nil {
log.Printf("Error making request to endpoint: %+v", err)
return p, err
}
req.Header.Add("Authorization", "Bearer")
req.Header.Add("Version", "2017-11-23")
resp, err := client.Do(req)
Is there a way to configure this on the client?
Use a function to encapsulate the code to create and configure the request:
func newRequest(endpoint string) (*http.Request, error) {
req, err := http.NewRequest("GET", fmt.Sprintf("https://%s", endpoint), nil)
if err != nil {
return nil, err
}
req.Header.Add("Authorization", "Bearer")
req.Header.Add("Version", "2017-11-23")
return req, nil
}
A more complicated approach is to implement a round tripper that adds the headers and delegates to another round tripper:
type transport struct {
underlyingTransport http.RoundTripper
}
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Add("Authorization", "Bearer")
req.Header.Add("Version", "2017-11-23")
return t.underlyingTransport.RoundTrip(req)
}
Use it to create a client like this:
c := http.Client{Transport: &transport{ underlyingTransport: http.DefaultTransport } }
The transport adds the headers on calls to c.Do(), c.Get(), c.Post(), etc.

golang TCPConn.SetWriteDeadline doesn't seem to work as expected

I'm trying to detect sending failures by inspecting the error returned by golang TCPConn.Write, but it's nil. I also tried using TCPConn.SetWriteDeadline without success.
That's how things happen:
the server starts
a client connects
the server sends a message and the client receives it
the client shuts down
the server sends one more message: no error
the server sends the third message: only now the error appears
Question: why only the second message to a non-existing client results in an error? How should the case be handled properly?
The code follows:
package main
import (
"net"
"os"
"bufio"
"fmt"
"time"
)
func AcceptConnections(listener net.Listener, console <- chan string) {
msg := ""
for {
conn, err := listener.Accept()
if err != nil {
panic(err)
}
fmt.Printf("client connected\n")
for {
if msg == "" {
msg = <- console
fmt.Printf("read from console: %s", msg)
}
err = conn.SetWriteDeadline(time.Now().Add(time.Second))
if err != nil {
fmt.Printf("SetWriteDeadline failed: %v\n", err)
}
_, err = conn.Write([]byte(msg))
if err != nil {
// expecting an error after sending a message
// to a non-existing client endpoint
fmt.Printf("failed sending a message to network: %v\n", err)
break
} else {
fmt.Printf("msg sent: %s", msg)
msg = ""
}
}
}
}
func ReadConsole(network chan <- string) {
console := bufio.NewReader(os.Stdin)
for {
line, err := console.ReadString('\n')
if err != nil {
panic(err)
} else {
network <- line
}
}
}
func main() {
listener, err := net.Listen("tcp", "localhost:6666")
if err != nil {
panic(err)
}
println("listening on " + listener.Addr().String())
consoleToNetwork := make(chan string)
go AcceptConnections(listener, consoleToNetwork)
ReadConsole(consoleToNetwork)
}
The server console looks like this:
listening on 127.0.0.1:6666
client connected
hi there!
read from console: hi there!
msg sent: hi there!
this one should fail
read from console: this one should fail
msg sent: this one should fail
this one actually fails
read from console: this one actually fails
failed sending a message to network: write tcp 127.0.0.1:51194: broken pipe
The client looks like this:
package main
import (
"net"
"os"
"io"
//"bufio"
//"fmt"
)
func cp(dst io.Writer, src io.Reader, errc chan<- error) {
// -reads from src and writes to dst
// -blocks until EOF
// -EOF is not an error
_, err := io.Copy(dst, src)
// push err to the channel when io.Copy returns
errc <- err
}
func StartCommunication(conn net.Conn) {
//create a channel for errors
errc := make(chan error)
//read connection and print to console
go cp(os.Stdout, conn, errc)
//read user input and write to connection
go cp(conn, os.Stdin, errc)
//wait until nil or an error arrives
err := <- errc
if err != nil {
println("cp error: ", err.Error())
}
}
func main() {
servAddr := "localhost:6666"
tcpAddr, err := net.ResolveTCPAddr("tcp", servAddr)
if err != nil {
println("ResolveTCPAddr failed:", err.Error())
os.Exit(1)
}
conn, err := net.DialTCP("tcp", nil, tcpAddr)
if err != nil {
println("net.DialTCP failed:", err.Error())
os.Exit(1)
}
defer conn.Close()
StartCommunication(conn)
}
EDIT: Following JimB's suggestion I came up with a working example. Messages don't get lost any more and are re-sent in a new connection. I'm not quite sure though how safe is it to use a shared variable (connWrap.IsFaulted) between different go routines.
package main
import (
"net"
"os"
"bufio"
"fmt"
)
type Connection struct {
IsFaulted bool
Conn net.Conn
}
func StartWritingToNetwork(connWrap * Connection, errChannel chan <- error, msgStack chan string) {
for {
msg := <- msgStack
if connWrap.IsFaulted {
//put it back for another connection
msgStack <- msg
return
}
_, err := connWrap.Conn.Write([]byte(msg))
if err != nil {
fmt.Printf("failed sending a message to network: %v\n", err)
connWrap.IsFaulted = true
msgStack <- msg
errChannel <- err
return
} else {
fmt.Printf("msg sent: %s", msg)
}
}
}
func StartReadingFromNetwork(connWrap * Connection, errChannel chan <- error){
network := bufio.NewReader(connWrap.Conn)
for (!connWrap.IsFaulted) {
line, err := network.ReadString('\n')
if err != nil {
fmt.Printf("failed reading from network: %v\n", err)
connWrap.IsFaulted = true
errChannel <- err
} else {
fmt.Printf("%s", line)
}
}
}
func AcceptConnections(listener net.Listener, console chan string) {
errChannel := make(chan error)
for {
conn, err := listener.Accept()
if err != nil {
panic(err)
}
fmt.Printf("client connected\n")
connWrap := Connection{false, conn}
go StartReadingFromNetwork(&connWrap, errChannel)
go StartWritingToNetwork(&connWrap, errChannel, console)
//block until an error occurs
<- errChannel
}
}
func ReadConsole(network chan <- string) {
console := bufio.NewReader(os.Stdin)
for {
line, err := console.ReadString('\n')
if err != nil {
panic(err)
} else {
network <- line
}
}
}
func main() {
listener, err := net.Listen("tcp", "localhost:6666")
if err != nil {
panic(err)
}
println("listening on " + listener.Addr().String())
consoleToNetwork := make(chan string)
go AcceptConnections(listener, consoleToNetwork)
ReadConsole(consoleToNetwork)
}
This isn't Go specific, and is a artifact of the underlying TCP socket showing through.
A decent diagram of the TCP termination steps is at the bottom of this page:
http://www.tcpipguide.com/free/t_TCPConnectionTermination-2.htm
The simple version is that when the client closes its socket, it sends a FIN, and receives an ACK from the server. It then waits for the server to do the same. Instead of sending a FIN though, you're sending more data, which is discarded, and the client socket now assumes that any more data coming from you is invalid, so the next time you send you get an RST, which is what bubbles up into the error you see.
Going back to your program, you need to handle this somehow. Generally you can think of whomever is in charge of initiating a send, is also in charge of initiating termination, hence your server should assume that it can continue to send until it closes the connection, or encounters an error. If you need to more reliably detect the client closing, you need to have some sort of client response in the protocol. That way recv can be called on the socket and return 0, which alerts you to the closed connection.
In go, this will return an EOF error from the connection's Read method (or from within the Copy in your case). SetWriteDeadline doesn't work because a small write will go though and get dropped silently, or the client will eventually respond with an RST, giving you an error.

Resources