4

I am creating a streaming API similar to the Twitter firehose/streaming API.

As far as I can gather this is based on HTTP connections that are kept open and when the backend gets data it then writes to the chucked HTTP connection. It seems that any code I write closes the HTTP connection as soon as anything connects.

Is there a way to keep this open at all?

func startHTTP(pathPrefix string) {
    log.Println("Starting HTTPS Server")
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // Wait here until a write happens to w
        // Or we timeout, we can reset this timeout after each write
    })

    log.Print("HTTPS listening on :5556")
    log.Fatal(http.ListenAndServeTLS(":5556", pathPrefix+".crt", pathPrefix+".key", nil))
}
Lee Armstrong
  • 11,420
  • 15
  • 74
  • 122
  • 3
    If you don't want to close the connection, don't return from the handler. The handler is what "handles" the connection. – JimB Jun 21 '17 at 13:01
  • Right, so it's getting to the end of function and returning and so closing the connection. What would be the best way to "block" the function and being able to write to w from another goroutine? – Lee Armstrong Jun 21 '17 at 13:02
  • you could try putting it into an infinite for loop that has select statements that way you can catch any errors that occur to close down any faulty connections – Joffutt4 Jun 21 '17 at 13:04
  • 5
    The same way you block in any function. You need to coordinate with the other goroutine, pass it the ResponseWriter, then wait for it to complete. If you have goroutines running for this, you must already have some sort of pattern for dispatching and waiting for them. – JimB Jun 21 '17 at 13:05
  • Ok, that makes sense, and each new HTTP connection spins up a goroutine too? – Lee Armstrong Jun 21 '17 at 13:06
  • 1
    Yes, each connection handler is in it's own goroutine; goroutines are how Go handles concurrency. – JimB Jun 21 '17 at 13:13

1 Answers1

10

When you want to send HTTP response to client not immediately but after some event, it's called long polling.

Here's simple example of long polling with request cancellation on client disconnect:

package main

import (
    "context"
    "fmt"
    "net/http"
    "time"
)

func longOperation(ctx context.Context, ch chan<- string) {
    // Simulate long operation.
    // Change it to more than 10 seconds to get server timeout.
    select {
    case <-time.After(time.Second * 3):
        ch <- "Successful result."
    case <-ctx.Done():
        close(ch)
    }
}

func handler(w http.ResponseWriter, _ *http.Request) {
    notifier, ok := w.(http.CloseNotifier)
    if !ok {
        panic("Expected http.ResponseWriter to be an http.CloseNotifier")
    }

    ctx, cancel := context.WithCancel(context.Background())
    ch := make(chan string)
    go longOperation(ctx, ch)

    select {
    case result := <-ch:
        fmt.Fprint(w, result)
        cancel()
        return
    case <-time.After(time.Second * 10):
        fmt.Fprint(w, "Server is busy.")
    case <-notifier.CloseNotify():
        fmt.Println("Client has disconnected.")
    }
    cancel()
    <-ch
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe("localhost:8080", nil)
}

URLs:

  1. anonymous struct and empty struct.
  2. Send a chunked HTTP response from a Go server.
  3. Go Concurrency Patterns: Context.

Gists:

  1. Golang long polling example.
  2. Golang long polling example with request cancellation.
berserkk
  • 987
  • 6
  • 11
  • Thanks. It's similar to what I ended up with in the end. What I am trying to work out is what happens if the client disconnects during the longOperation – Lee Armstrong Jun 21 '17 at 19:20
  • 1
    Check `CloseNotifier` https://golang.org/pkg/net/http/#CloseNotifier and `context` package https://blog.golang.org/context – berserkk Jun 21 '17 at 19:28
  • 1
    I've updated my answer a little bit to make it more full. – berserkk Jun 21 '17 at 21:20
  • 1
    WebSockets is the modern alternative to long polling ... connection remains open so traffic may be sent from either client or server side on the opened websocket – Scott Stensland Jun 22 '17 at 00:26
  • @berserkk nice, the use of context I hadn't considered but that tidies it up very nicely! – Lee Armstrong Jun 22 '17 at 07:12