Recently, I started making various things using Spring WebFlux. With Spring WebFlux,
@GetMapping
public Flux<Hoge> all() {
return Flux.create(sink -> {
// ...
})
}
I write a description in the Controller like this, but how do I receive this in the browser? I did a little research and tried it, so I will write it.
This article will be very helpful. BLOG.IK.AM --First Spring WebFlux (Part 1 --Try Spring WebFlux)
It seems that it will be returned in the format of Content-Type: text / event-stream
(Server-Sent Event
) or Content-Type: application / stream + json
.
Each seems to return the body in the following format.
Content-Type: text/event-stream
data:{"key":"value" ... }
data:{"key":"value" ... }
data:{"key":"value" ... }
Content-Type: application/stream+json
{"key":"value" ... }
{"key":"value" ... }
{"key":"value" ... }
When you google Server-Sent Events
, you can see [Event Source] in the explanation of Using Server-Sent Events | MDN. I quickly realized that I would use it, but what about ʻapplication / stream + json`?
Using the Fetch API, it seems that you can access it as follows.
const url = "..."; //URL to request
const callback = json => { /* ...Logic to process each row JSON*/ };
const decoder = new TextDecoder();
const abortController = new AbortController();
const { signal } = abortController;
//start fetch
fetch(url, {
signal,
headers: {
Accept: "application/stream+json"
}
}).then(response => {
let buffer = "";
/**
*Process the substring read from the stream
* @param {string}chunk Read string
* @returns {void}
*/
function consumeChunk(chunk) {
buffer += chunk;
//Split by line feed code and fetch each JSON line
// https://en.wikipedia.org/wiki/JSON_streaming#Line-delimited_JSON
// http://jsonlines.org/
const re = /(.*?)(\r\n|\r|\n)/g;
let result;
let lastIndex = 0;
while ((result = re.exec(buffer))) {
const data = result[1];
if (data.trim()) {
callback(JSON.parse(data)); //Pass JSON to the callback to process.
}
({ lastIndex } = re);
}
//If there is no newline code, the rest of the string will be combined with the next read for processing.
buffer = buffer.slice(lastIndex);
}
//Create a stream reader
const reader = response.body.getReader();
//Reading process body
function readNext() {
return reader.read().then(({ done, value }) => {
if (!done) {
// --Read process--
consumeChunk(decoder.decode(value));
return readNext();
}
// --End processing--
if (buffer.trim()) {
//If there is a character string left at the end, it will be processed.
//This is reached if the line feed code is not included after the last line data.
//It doesn't seem to happen in Spring WebFlux, but http://jsonlines.org/Looking at, I got the impression that the last line feed code may not be available, so I will implement it just in case.
// `consumeChunk`Does not recognize the line unless you pass a line feed code, so pass a line feed code.
consumeChunk("\n");
}
return response;
});
}
return readNext();
});
}
I will explain what I am doing below.
This has nothing to do with fetching Flux, but it is necessary if you want to cancel fetch
in the middle.
const abortController = new AbortController();
const { signal } = abortController;
//start fetch
fetch(url, {
signal,
headers: {
Accept: "application/stream+json"
}
})
If you pass signal
to the option of fetch
.
abortController.abort()
Then you can stop fetch
in the middle.
At this time, it seems even better to interrupt the process if it is canceled even on the Java Controller side.
@GetMapping
public Flux<Hoge> all() {
return Flux.create(sink -> {
// ...
if (sink.isCancelled()) {
//Since it has been canceled, let's interrupt the process.
}
// ...
})
}
You can also use ʻon Cancel`.
response
in StreamRefer to ReadableStream.getReader () | MDN.
Using ReadableStream, it seems that the response can be received by Stream as follows.
fetch(...).then(response => {
// ...
//Create a stream reader
const reader = response.body.getReader();
function readNext() {
return reader.read().then(({ done, value }) => {
if (!done) {
/*Handle value*/
// ...
return readNext();
}
return; /*End*/
});
}
return readNext();
})
value
The value
obtained from ReadableStream is [Uint8Array](https://developer.mozilla.org/en/ It seems to come by docs / Web / JavaScript / Reference / Global_Objects / Uint8Array), so decode it using TextDecoder.
const decoder = new TextDecoder();
// ...
const sValue = decoder.decode(value) //Decode Uint8Array and convert it to string
chunk
Up to this point, we have reached the point where we can get the contents of Stream as a character string. Here we will actually parse JSON.
let buffer = "";
/**
*Process the substring read from the stream
* @param {string}chunk Read string
* @returns {void}
*/
function consumeChunk(chunk) {
buffer += chunk;
//Split by line feed code and fetch each JSON line
// https://en.wikipedia.org/wiki/JSON_streaming#Line-delimited_JSON
// http://jsonlines.org/
const re = /(.*?)(\r\n|\r|\n)/g;
let result;
let lastIndex = 0;
while ((result = re.exec(buffer))) {
const data = result[1];
if (data.trim()) {
callback(JSON.parse(data)); //Pass JSON to the callback to process.
}
({ lastIndex } = re);
}
//If there is no newline code, the rest of the string will be combined with the next read for processing.
buffer = buffer.slice(lastIndex);
}
I'm doing something, but I'll explain it later.
ʻApplication / stream + json` returns the following response as described above.
{"key":"value" ... }
{"key":"value" ... }
{"key":"value" ... }
This response is JSON separated by a newline code.
So, each line JSON is JSON.parse
separated by a line feed code.
The string obtained with read ()
from ReadableStream is not a complete JSON. It is just a part of the whole response.
Therefore, if the chunk does not contain a line feed code, it is combined with the next chunk for processing.
That's all for the explanation.
The method using EventSource may not be able to pass header information, and may require some ingenuity or modification depending on the application to be embedded. (For example, when processing on the server side on the assumption that the authentication token is placed in the header.)
However, the method using this Fetch API is almost the same as normal HTTP access, so I had the impression that it could be used easily (if the browser supports Fetch API).
Recommended Posts