'Returning JSON data as a stream per chunk to Angular2 or jQuery over HTTP2 (HTTPS)

In one of my API's I mostly return a result (let's say paged 50 results) as one whole in an json array like so:

[{},{},{},{},{},...]

I was wondering if there are better ways of doing this over HTTP2 (as it has many new partial streaming features) with Go's HTTP server (using HTTPS in Gin for this project).

Maybe I could chunk every {} result and send them as segments on a stream? How would the AJAX call in Angular or jQuery know that there's a new chunk delivered (newline or some character marker?)? And what call in the library could actually handle such a multi-promise (does that even exist? :P)? Could I benefit from the HTTP2 stream-features in some way to prevent multiple-connections from opening?

I'm kind of aiming to have the results nicely plop into the list as they come in.

UPDATE

Maybe it's easier to use Keep-Alive header in some way to let's keep the connection open for for a certain amount of seconds to be able to stream over and fire many smaller requests/responses?



Solution 1:[1]

As I see, application/x-ndjson could help you.

Here're an example that uses standard net/http package. You could port to GIN also.

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "time"
)

// let's generate some fake data
func sampleStream() <-chan []byte {
    out := make(chan []byte, 10)
    go func() {
        for i := 0; i < 10; i++ {
            d := map[string]interface{}{"hello": i}
            buf, _ := json.Marshal(d)
            out <- buf
            time.Sleep(200 * time.Millisecond)
        }
        close(out)
    }()
    return out
}

func streamJSON(w http.ResponseWriter, r *http.Request) {
    flusher, ok := w.(http.Flusher)
    if !ok {
        w.WriteHeader(500)
        w.Write([]byte("Server unsupport flush"))
        return
    }

    // proper handle cors !!!
    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Content-Type", "application/x-ndjson")
    w.Header().Set("Connection", "Keep-Alive")
    w.Header().Set("X-Content-Type-Options", "nosniff")
    for buf := range sampleStream() {
        w.Write(buf)
        w.Write([]byte("\n"))
        flusher.Flush()
    }
}

func main() {
    http.HandleFunc("/", streamJSON)
    s := &http.Server{
        Addr: ":8080",
    }
    fmt.Println("Listen :8080")
    if err := s.ListenAndServe(); err != nil {
        panic(err)
    }
}

And simple JS:

const abortController = new AbortController();

async function main() {
  const res = await fetch("http://localhost:8080", {
    signal: abortController.signal,
  });
  const reader = res.body.getReader();
  const textDecoder = new TextDecoder();
  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) {
        break;
      }
      const text = textDecoder.decode(value);
      const json = JSON.parse(text);
      console.log(json);
    }
  } finally {
    reader.releaseLock();
  }
}

Solution 2:[2]

Lookup Server Sent Events. It's essentially chunked response for which the browser already has built-in support.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 hienduyph
Solution 2