github.com/go-swagger/go-swagger@v0.31.0/examples/stream-server/README.md (about)

     1  # Streaming with go-swagger
     2  
     3  ## Purpose
     4  
     5  This directory contains a project that shows how to generate with `go-swagger`:
     6  1. a server that can return a stream of newline-delimited JSON bodies.
     7  2. a client that can read this stream.
     8  
     9  ## Build and run a streaming server
    10  (All following instructuctions are to be run from the directory parallel to this file.)
    11  
    12  1. Generate the code: `$ swagger generate server -f swagger.yml`
    13  2. Install the project: `$ go install ./...`
    14  3. Run the server: `$ $GOPATH/bin/countdown-server --port=8000`
    15  
    16  ### See the streaming output
    17  In another terminal window, request some streaming output:
    18  ```
    19  $ curl -v http://127.0.0.1:8000/elapse/5
    20  * About to connect() to 127.0.0.1 port 8000 (#0)
    21  *   Trying 127.0.0.1...
    22  * Adding handle: conn: 0x7fdd8400a600
    23  * Adding handle: send: 0
    24  * Adding handle: recv: 0
    25  * Curl_addHandleToPipeline: length: 1
    26  * - Conn 0 (0x7fdd8400a600) send_pipe: 1, recv_pipe: 0
    27  * Connected to 127.0.0.1 (127.0.0.1) port 8000 (#0)
    28  > GET /elapse/5 HTTP/1.1
    29  > User-Agent: curl/7.30.0
    30  > Host: 127.0.0.1:8000
    31  > Accept: */*
    32  >
    33  < HTTP/1.1 200 OK
    34  < Content-Type: application/json
    35  < Date: Sun, 11 Sep 2016 00:54:34 GMT
    36  < Transfer-Encoding: chunked
    37  <
    38  {"remains":5}
    39  {"remains":4}
    40  {"remains":3}
    41  {"remains":2}
    42  {"remains":1}
    43  {"remains":0}
    44  * Connection #0 to host 127.0.0.1 left intact
    45  $
    46  ```
    47  ### See an error condition
    48  Also in another terminal window, see an error message (not streaming):
    49  ```
    50  $ curl -v http://127.0.0.1:8000/elapse/11
    51  * About to connect() to 127.0.0.1 port 8000 (#0)
    52  *   Trying 127.0.0.1...
    53  * Adding handle: conn: 0x7f8582004000
    54  * Adding handle: send: 0
    55  * Adding handle: recv: 0
    56  * Curl_addHandleToPipeline: length: 1
    57  * - Conn 0 (0x7f8582004000) send_pipe: 1, recv_pipe: 0
    58  * Connected to 127.0.0.1 (127.0.0.1) port 8000 (#0)
    59  > GET /elapse/11 HTTP/1.1
    60  > User-Agent: curl/7.30.0
    61  > Host: 127.0.0.1:8000
    62  > Accept: */*
    63  >
    64  < HTTP/1.1 403 Forbidden
    65  < Content-Type: application/json
    66  < Date: Sun, 11 Sep 2016 00:54:48 GMT
    67  < Content-Length: 0
    68  <
    69  * Connection #0 to host 127.0.0.1 left intact
    70  $
    71  ```
    72  
    73  ## Build and run a streaming client
    74  
    75  The client library in this folder has been generated with `swagger generate client -f swagger.yml --skip-models`
    76  (assuming the models where built in the previous step).
    77  
    78  A sample client using this library is provided here: `elapsed_client.go`.
    79  
    80  This client reads asynchronously from the stream of json produced by the server above (from `http://localhost:8000`)
    81  unmarshals and print the result. The client maintains the connection to receive chunks, for up to 7 seconds.
    82  
    83  This program takes the start of the countdown as a command line argument.
    84  
    85  ### Try it
    86  
    87  ```
    88  go run elapsed_client.go 5
    89  
    90  2021/01/17 14:58:22 asking server for countdown timings: 5
    91  2021/01/17 14:58:22 received countdown mark - raw: {"remains":5}
    92  2021/01/17 14:58:22 received countdown mark - remaining: 5
    93  2021/01/17 14:58:23 received countdown mark - raw: {"remains":4}
    94  2021/01/17 14:58:23 received countdown mark - remaining: 4
    95  2021/01/17 14:58:24 received countdown mark - raw: {"remains":3}
    96  2021/01/17 14:58:24 received countdown mark - remaining: 3
    97  2021/01/17 14:58:25 received countdown mark - raw: {"remains":2}
    98  2021/01/17 14:58:25 received countdown mark - remaining: 2
    99  2021/01/17 14:58:26 received countdown mark - raw: {"remains":1}
   100  2021/01/17 14:58:26 received countdown mark - remaining: 1
   101  2021/01/17 14:58:27 received countdown mark - raw: {"remains":0}
   102  2021/01/17 14:58:27 received countdown mark - remaining: 0
   103  2021/01/17 14:58:27 response complete
   104  2021/01/17 14:58:27 EOF
   105  ```
   106  
   107  ```
   108  go run elapsed_client.go 8
   109  
   110  2021/01/17 14:58:31 asking server for countdown timings: 8
   111  2021/01/17 14:58:31 received countdown mark - raw: {"remains":8}
   112  2021/01/17 14:58:31 received countdown mark - remaining: 8
   113  2021/01/17 14:58:32 received countdown mark - raw: {"remains":7}
   114  2021/01/17 14:58:32 received countdown mark - remaining: 7
   115  2021/01/17 14:58:33 received countdown mark - raw: {"remains":6}
   116  2021/01/17 14:58:33 received countdown mark - remaining: 6
   117  2021/01/17 14:58:34 received countdown mark - raw: {"remains":5}
   118  2021/01/17 14:58:34 received countdown mark - remaining: 5
   119  2021/01/17 14:58:35 received countdown mark - raw: {"remains":4}
   120  2021/01/17 14:58:35 received countdown mark - remaining: 4
   121  2021/01/17 14:58:36 received countdown mark - raw: {"remains":3}
   122  2021/01/17 14:58:36 received countdown mark - remaining: 3
   123  2021/01/17 14:58:37 received countdown mark - raw: {"remains":2}
   124  2021/01/17 14:58:37 received countdown mark - remaining: 2
   125  2021/01/17 14:58:38 got an error
   126  2021/01/17 14:58:38 EOF
   127  2021/01/17 14:58:38 failure: context deadline exceeded
   128  ```
   129  
   130  ### How does it work?
   131  
   132  #### Setting the right consumer
   133  
   134  First and foremost, we have to realize that the "application/json" mime is not really
   135  describing our API. Rather, the server _streams_ chunks of individual JSON bits.
   136  
   137  The runtime does not automatically detect that fact, we need to override this, like so:
   138  ```go
   139  import (
   140    ...
   141  	"github.com/go-openapi/runtime"
   142  	httptransport "github.com/go-openapi/runtime/client"
   143  	"github.com/go-swagger/go-swagger/examples/stream-server/client"
   144    ...
   145  )
   146  
   147  	customized := httptransport.New("localhost:8000", "/", []string{"http"})
   148  	customized.Consumers[runtime.JSONMime] = runtime.ByteStreamConsumer()
   149  	countdowns := client.New(customized, nil)
   150  ```
   151  
   152  This tells the runtime to use a `ByteStreamConsumer` instead of a `JSONConsumer` when consuming a response.
   153  
   154  #### Consuming asynchronously
   155  
   156  The runtime consumer performs a "io.Copy()" call from the body to the writer passed by the request.
   157  
   158  If we don't want to block until this is complete, we may pass an `io.PipeWriter` as the writer for this request. Like so:
   159  
   160  ```go
   161  	reader, writer := io.Pipe()
   162    ...
   163  	_, err := countdowns.Operations.Elapse(elapsed, writer)
   164  ```
   165  
   166  The `reader` side of this pipe may be consuming by another go routine.
   167  
   168  #### Unmarshalling the stream
   169  
   170  The response is just a stream of byte, so the client has to unmarshal the messages received unitarily.
   171  In this example, the stream is separated by line feed, so we can use a `bufio.Scanner` to do the job.
   172  
   173  Notice the use of the `cancel()` method to interrupt the ongoing request if the go routine fails.
   174  
   175  ```go
   176      ...
   177  		// read response items line by line
   178  		for scanner.Scan() {
   179  			// each response item is JSON
   180  			txt := scanner.Text()
   181  			log.Printf("received countdown mark - raw: %s", txt)
   182  
   183  			var mark models.Mark
   184  
   185  			err := json.Unmarshal([]byte(txt), &mark)
   186  			if err != nil {
   187  				log.Printf("unmarshal error: %v", err)
   188  				return
   189  			}
   190  
   191  			log.Printf("received countdown mark - remaining: %d", swag.Int64Value(mark.Remains))
   192  		}
   193      ...
   194  ```