github.com/circl-dev/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/circl-dev/runtime" 142 httptransport "github.com/circl-dev/runtime/client" 143 "github.com/circl-dev/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 ```