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

2

9

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?

Variform answered 12/6, 2016 at 21:15 Comment(3)
This is not a full answer but have you looked into JSON Lines as your streaming pattern?Schizophyceous
maybe this can help? medium.com/stupid-gopher-tricks/…Arbitress
HTTP2 will automatically manage your connections to make this as efficient as possible, so unless you need extreme performance you probably don't need to worry about it. That said, you could look into using websockets if you really want to stream the data.Cellule
C
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();
  }
}
Concentre answered 19/4, 2022 at 13:39 Comment(0)
A
0

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

Amandy answered 10/12, 2021 at 11:37 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Dogear

© 2022 - 2024 — McMap. All rights reserved.