mirror of
https://github.com/amir20/dozzle.git
synced 2025-12-24 06:28:42 +01:00
Fixes reconnection bugs (#1256)
* Fixes reconnect by sending lastEventId see #1246 * Cleans up colors and spacing * Fixes tests
This commit is contained in:
@@ -27,7 +27,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot="{ setLoading }">
|
<template v-slot="{ setLoading }">
|
||||||
<log-viewer-with-source :id="id" @loading-more="setLoading($event)" ref="source"></log-viewer-with-source>
|
<log-viewer-with-source :id="id" @loading-more="setLoading($event)"></log-viewer-with-source>
|
||||||
</template>
|
</template>
|
||||||
</scrollable-view>
|
</scrollable-view>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -66,22 +66,24 @@ describe("<LogEventSource />", () => {
|
|||||||
|
|
||||||
test("should connect to EventSource", async () => {
|
test("should connect to EventSource", async () => {
|
||||||
const wrapper = createLogEventSource();
|
const wrapper = createLogEventSource();
|
||||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||||
expect(sources["/api/logs/stream?id=abc"].readyState).toBe(1);
|
expect(sources["/api/logs/stream?id=abc&lastEventId="].readyState).toBe(1);
|
||||||
wrapper.destroy();
|
wrapper.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should close EventSource", async () => {
|
test("should close EventSource", async () => {
|
||||||
const wrapper = createLogEventSource();
|
const wrapper = createLogEventSource();
|
||||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||||
wrapper.destroy();
|
wrapper.destroy();
|
||||||
expect(sources["/api/logs/stream?id=abc"].readyState).toBe(2);
|
expect(sources["/api/logs/stream?id=abc&lastEventId="].readyState).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should parse messages", async () => {
|
test("should parse messages", async () => {
|
||||||
const wrapper = createLogEventSource();
|
const wrapper = createLogEventSource();
|
||||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||||
sources["/api/logs/stream?id=abc"].emitMessage({ data: `2019-06-12T10:55:42.459034602Z "This is a message."` });
|
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
|
||||||
|
data: `2019-06-12T10:55:42.459034602Z "This is a message."`,
|
||||||
|
});
|
||||||
|
|
||||||
const [message, _] = wrapper.vm.messages;
|
const [message, _] = wrapper.vm.messages;
|
||||||
const { key, ...messageWithoutKey } = message;
|
const { key, ...messageWithoutKey } = message;
|
||||||
@@ -90,15 +92,15 @@ describe("<LogEventSource />", () => {
|
|||||||
expect(messageWithoutKey).toMatchInlineSnapshot(`
|
expect(messageWithoutKey).toMatchInlineSnapshot(`
|
||||||
Object {
|
Object {
|
||||||
"date": 2019-06-12T10:55:42.459Z,
|
"date": 2019-06-12T10:55:42.459Z,
|
||||||
"message": " \\"This is a message.\\"",
|
"message": "\\"This is a message.\\"",
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should parse messages with loki's timestamp format", async () => {
|
test("should parse messages with loki's timestamp format", async () => {
|
||||||
const wrapper = createLogEventSource();
|
const wrapper = createLogEventSource();
|
||||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||||
sources["/api/logs/stream?id=abc"].emitMessage({ data: `2020-04-27T12:35:43.272974324+02:00 xxxxx` });
|
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({ data: `2020-04-27T12:35:43.272974324+02:00 xxxxx` });
|
||||||
|
|
||||||
const [message, _] = wrapper.vm.messages;
|
const [message, _] = wrapper.vm.messages;
|
||||||
const { key, ...messageWithoutKey } = message;
|
const { key, ...messageWithoutKey } = message;
|
||||||
@@ -107,15 +109,17 @@ describe("<LogEventSource />", () => {
|
|||||||
expect(messageWithoutKey).toMatchInlineSnapshot(`
|
expect(messageWithoutKey).toMatchInlineSnapshot(`
|
||||||
Object {
|
Object {
|
||||||
"date": 2020-04-27T10:35:43.272Z,
|
"date": 2020-04-27T10:35:43.272Z,
|
||||||
"message": " xxxxx",
|
"message": "xxxxx",
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should pass messages to slot", async () => {
|
test("should pass messages to slot", async () => {
|
||||||
const wrapper = createLogEventSource();
|
const wrapper = createLogEventSource();
|
||||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||||
sources["/api/logs/stream?id=abc"].emitMessage({ data: `2019-06-12T10:55:42.459034602Z "This is a message."` });
|
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
|
||||||
|
data: `2019-06-12T10:55:42.459034602Z "This is a message."`,
|
||||||
|
});
|
||||||
const [message, _] = wrapper.findComponent(LogViewer).vm.messages;
|
const [message, _] = wrapper.findComponent(LogViewer).vm.messages;
|
||||||
|
|
||||||
const { key, ...messageWithoutKey } = message;
|
const { key, ...messageWithoutKey } = message;
|
||||||
@@ -125,7 +129,7 @@ describe("<LogEventSource />", () => {
|
|||||||
expect(messageWithoutKey).toMatchInlineSnapshot(`
|
expect(messageWithoutKey).toMatchInlineSnapshot(`
|
||||||
Object {
|
Object {
|
||||||
"date": 2019-06-12T10:55:42.459Z,
|
"date": 2019-06-12T10:55:42.459Z,
|
||||||
"message": " \\"This is a message.\\"",
|
"message": "\\"This is a message.\\"",
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
@@ -147,91 +151,93 @@ describe("<LogEventSource />", () => {
|
|||||||
|
|
||||||
test("should render messages", async () => {
|
test("should render messages", async () => {
|
||||||
const wrapper = createLogEventSource();
|
const wrapper = createLogEventSource();
|
||||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||||
sources["/api/logs/stream?id=abc"].emitMessage({ data: `2019-06-12T10:55:42.459034602Z "This is a message."` });
|
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
|
||||||
|
data: `2019-06-12T10:55:42.459034602Z "This is a message."`,
|
||||||
|
});
|
||||||
|
|
||||||
await wrapper.vm.$nextTick();
|
await wrapper.vm.$nextTick();
|
||||||
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
||||||
<ul class="events medium">
|
<ul class="events medium">
|
||||||
<li class=""><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55:42 AM</time></span> <span class="text"> "This is a message."</span></li>
|
<li><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55:42 AM</time></span> <span class="text">"This is a message."</span></li>
|
||||||
</ul>
|
</ul>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should render messages with color", async () => {
|
test("should render messages with color", async () => {
|
||||||
const wrapper = createLogEventSource();
|
const wrapper = createLogEventSource();
|
||||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||||
sources["/api/logs/stream?id=abc"].emitMessage({
|
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
|
||||||
data: `2019-06-12T10:55:42.459034602Z \x1b[30mblack\x1b[37mwhite`,
|
data: `2019-06-12T10:55:42.459034602Z \x1b[30mblack\x1b[37mwhite`,
|
||||||
});
|
});
|
||||||
|
|
||||||
await wrapper.vm.$nextTick();
|
await wrapper.vm.$nextTick();
|
||||||
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
||||||
<ul class="events medium">
|
<ul class="events medium">
|
||||||
<li class=""><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55:42 AM</time></span> <span class="text"> <span style="color:#000">black<span style="color:#AAA">white</span></span></span></li>
|
<li><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55:42 AM</time></span> <span class="text"><span style="color:#000">black<span style="color:#AAA">white</span></span></span></li>
|
||||||
</ul>
|
</ul>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should render messages with html entities", async () => {
|
test("should render messages with html entities", async () => {
|
||||||
const wrapper = createLogEventSource();
|
const wrapper = createLogEventSource();
|
||||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||||
sources["/api/logs/stream?id=abc"].emitMessage({
|
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
|
||||||
data: `2019-06-12T10:55:42.459034602Z <test>foo bar</test>`,
|
data: `2019-06-12T10:55:42.459034602Z <test>foo bar</test>`,
|
||||||
});
|
});
|
||||||
|
|
||||||
await wrapper.vm.$nextTick();
|
await wrapper.vm.$nextTick();
|
||||||
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
||||||
<ul class="events medium">
|
<ul class="events medium">
|
||||||
<li class=""><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55:42 AM</time></span> <span class="text"> <test>foo bar</test></span></li>
|
<li><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55:42 AM</time></span> <span class="text"><test>foo bar</test></span></li>
|
||||||
</ul>
|
</ul>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should render dates with 12 hour style", async () => {
|
test("should render dates with 12 hour style", async () => {
|
||||||
const wrapper = createLogEventSource({ hourStyle: "12" });
|
const wrapper = createLogEventSource({ hourStyle: "12" });
|
||||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||||
sources["/api/logs/stream?id=abc"].emitMessage({
|
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
|
||||||
data: `2019-06-12T23:55:42.459034602Z <test>foo bar</test>`,
|
data: `2019-06-12T23:55:42.459034602Z <test>foo bar</test>`,
|
||||||
});
|
});
|
||||||
|
|
||||||
await wrapper.vm.$nextTick();
|
await wrapper.vm.$nextTick();
|
||||||
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
||||||
<ul class="events medium">
|
<ul class="events medium">
|
||||||
<li class=""><span class="date"><time datetime="2019-06-12T23:55:42.459Z">today at 11:55:42 PM</time></span> <span class="text"> <test>foo bar</test></span></li>
|
<li><span class="date"><time datetime="2019-06-12T23:55:42.459Z">today at 11:55:42 PM</time></span> <span class="text"><test>foo bar</test></span></li>
|
||||||
</ul>
|
</ul>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should render dates with 24 hour style", async () => {
|
test("should render dates with 24 hour style", async () => {
|
||||||
const wrapper = createLogEventSource({ hourStyle: "24" });
|
const wrapper = createLogEventSource({ hourStyle: "24" });
|
||||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||||
sources["/api/logs/stream?id=abc"].emitMessage({
|
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
|
||||||
data: `2019-06-12T23:55:42.459034602Z <test>foo bar</test>`,
|
data: `2019-06-12T23:55:42.459034602Z <test>foo bar</test>`,
|
||||||
});
|
});
|
||||||
|
|
||||||
await wrapper.vm.$nextTick();
|
await wrapper.vm.$nextTick();
|
||||||
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
||||||
<ul class="events medium">
|
<ul class="events medium">
|
||||||
<li class=""><span class="date"><time datetime="2019-06-12T23:55:42.459Z">today at 23:55:42</time></span> <span class="text"> <test>foo bar</test></span></li>
|
<li><span class="date"><time datetime="2019-06-12T23:55:42.459Z">today at 23:55:42</time></span> <span class="text"><test>foo bar</test></span></li>
|
||||||
</ul>
|
</ul>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should render messages with filter", async () => {
|
test("should render messages with filter", async () => {
|
||||||
const wrapper = createLogEventSource({ searchFilter: "test" });
|
const wrapper = createLogEventSource({ searchFilter: "test" });
|
||||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||||
sources["/api/logs/stream?id=abc"].emitMessage({
|
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
|
||||||
data: `2019-06-11T10:55:42.459034602Z Foo bar`,
|
data: `2019-06-11T10:55:42.459034602Z Foo bar`,
|
||||||
});
|
});
|
||||||
sources["/api/logs/stream?id=abc"].emitMessage({
|
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
|
||||||
data: `2019-06-12T10:55:42.459034602Z This is a test <hi></hi>`,
|
data: `2019-06-12T10:55:42.459034602Z This is a test <hi></hi>`,
|
||||||
});
|
});
|
||||||
|
|
||||||
await wrapper.vm.$nextTick();
|
await wrapper.vm.$nextTick();
|
||||||
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
||||||
<ul class="events medium">
|
<ul class="events medium">
|
||||||
<li class=""><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55:42 AM</time></span> <span class="text"> This is a <mark>test</mark> <hi></hi></span></li>
|
<li><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55:42 AM</time></span> <span class="text">This is a <mark>test</mark> <hi></hi></span></li>
|
||||||
</ul>
|
</ul>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,10 +22,11 @@ export default {
|
|||||||
return {
|
return {
|
||||||
messages: [],
|
messages: [],
|
||||||
buffer: [],
|
buffer: [],
|
||||||
|
es: null,
|
||||||
|
lastEventId: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.es = null;
|
|
||||||
this.loadLogs();
|
this.loadLogs();
|
||||||
this.flushBuffer = debounce(this.flushNow, 250, { maxWait: 1000 });
|
this.flushBuffer = debounce(this.flushNow, 250, { maxWait: 1000 });
|
||||||
},
|
},
|
||||||
@@ -33,28 +34,37 @@ export default {
|
|||||||
this.es.close();
|
this.es.close();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onContainerStateChange(newValue, oldValue) {
|
|
||||||
if (newValue == "running" && newValue != oldValue) {
|
|
||||||
this.connect();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
loadLogs() {
|
loadLogs() {
|
||||||
this.reset();
|
this.reset();
|
||||||
this.connect();
|
this.connect();
|
||||||
},
|
},
|
||||||
|
onContainerStopped() {
|
||||||
|
this.es.close();
|
||||||
|
this.buffer.push({ event: "container-stopped", message: "Container stopped", date: new Date(), key: new Date() });
|
||||||
|
this.flushBuffer();
|
||||||
|
this.flushBuffer.flush();
|
||||||
|
},
|
||||||
|
onMessage(e) {
|
||||||
|
this.lastEventId = e.lastEventId;
|
||||||
|
this.buffer.push(this.parseMessage(e.data));
|
||||||
|
this.flushBuffer();
|
||||||
|
},
|
||||||
|
onContainerStateChange(newValue, oldValue) {
|
||||||
|
if (newValue == "running" && newValue != oldValue) {
|
||||||
|
this.buffer.push({
|
||||||
|
event: "container-started",
|
||||||
|
message: "Container started",
|
||||||
|
date: new Date(),
|
||||||
|
key: new Date(),
|
||||||
|
});
|
||||||
|
this.connect();
|
||||||
|
}
|
||||||
|
},
|
||||||
connect() {
|
connect() {
|
||||||
this.es = new EventSource(`${config.base}/api/logs/stream?id=${this.id}`);
|
this.es = new EventSource(`${config.base}/api/logs/stream?id=${this.id}&lastEventId=${this.lastEventId ?? ""}`);
|
||||||
this.es.addEventListener("container-stopped", (e) => {
|
this.es.addEventListener("container-stopped", (e) => this.onContainerStopped());
|
||||||
this.es.close();
|
|
||||||
this.buffer.push({ event: "container-stopped", message: "Container stopped", date: new Date() });
|
|
||||||
this.flushBuffer();
|
|
||||||
this.flushBuffer.flush();
|
|
||||||
});
|
|
||||||
this.es.addEventListener("error", (e) => console.error("EventSource failed: " + JSON.stringify(e)));
|
this.es.addEventListener("error", (e) => console.error("EventSource failed: " + JSON.stringify(e)));
|
||||||
this.es.onmessage = (e) => {
|
this.es.onmessage = (e) => this.onMessage(e);
|
||||||
this.buffer.push(this.parseMessage(e.data));
|
|
||||||
this.flushBuffer();
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
flushNow() {
|
flushNow() {
|
||||||
this.messages.push(...this.buffer);
|
this.messages.push(...this.buffer);
|
||||||
@@ -96,7 +106,7 @@ export default {
|
|||||||
}
|
}
|
||||||
const key = data.substring(0, i);
|
const key = data.substring(0, i);
|
||||||
const date = new Date(key);
|
const date = new Date(key);
|
||||||
const message = data.substring(i);
|
const message = data.substring(i + 1);
|
||||||
return { key, date, message };
|
return { key, date, message };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<ul class="events" :class="settings.size">
|
<ul class="events" :class="settings.size">
|
||||||
<li v-for="item in filtered" :key="item.key" :class="{ event: !!item.event }">
|
<li v-for="item in filtered" :key="item.key" :data-event="item.event">
|
||||||
<span class="date" v-if="settings.showTimestamp"><relative-time :date="item.date"></relative-time></span>
|
<span class="date" v-if="settings.showTimestamp"><relative-time :date="item.date"></relative-time></span>
|
||||||
<span class="text" v-html="colorize(item.message)"></span>
|
<span class="text" v-html="colorize(item.message)"></span>
|
||||||
</li>
|
</li>
|
||||||
@@ -73,6 +73,12 @@ export default {
|
|||||||
scroll-snap-align: end;
|
scroll-snap-align: end;
|
||||||
scroll-margin-block-end: 5rem;
|
scroll-margin-block-end: 5rem;
|
||||||
}
|
}
|
||||||
|
&[data-event="container-stopped"] {
|
||||||
|
color: #f14668;
|
||||||
|
}
|
||||||
|
&[data-event="container-started"] {
|
||||||
|
color: hsl(141, 53%, 53%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.small {
|
&.small {
|
||||||
@@ -104,10 +110,6 @@ export default {
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
li.event {
|
|
||||||
color: #f14668;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep mark {
|
::v-deep mark {
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background-color: var(--secondary-color);
|
background-color: var(--secondary-color);
|
||||||
|
|||||||
11
web/logs.go
11
web/logs.go
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -88,7 +87,12 @@ func (h *handler) streamLogs(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Header().Set("Connection", "keep-alive")
|
w.Header().Set("Connection", "keep-alive")
|
||||||
w.Header().Set("X-Accel-Buffering", "no")
|
w.Header().Set("X-Accel-Buffering", "no")
|
||||||
|
|
||||||
reader, err := h.client.ContainerLogs(r.Context(), container.ID, h.config.TailSize, r.Header.Get("Last-Event-ID"))
|
lastEventId := r.Header.Get("Last-Event-ID")
|
||||||
|
if len(r.URL.Query().Get("lastEventId")) > 0 {
|
||||||
|
lastEventId = r.URL.Query().Get("lastEventId")
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := h.client.ContainerLogs(r.Context(), container.ID, h.config.TailSize, lastEventId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
fmt.Fprintf(w, "event: container-stopped\ndata: end of stream\n\n")
|
fmt.Fprintf(w, "event: container-stopped\ndata: end of stream\n\n")
|
||||||
@@ -100,11 +104,10 @@ func (h *handler) streamLogs(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
|
|
||||||
|
|
||||||
buffered := bufio.NewReader(reader)
|
buffered := bufio.NewReader(reader)
|
||||||
var readerError error
|
var readerError error
|
||||||
var message string
|
var message string
|
||||||
for {
|
for {
|
||||||
message, readerError = buffered.ReadString('\n')
|
message, readerError = buffered.ReadString('\n')
|
||||||
fmt.Fprintf(w, "data: %s\n", message)
|
fmt.Fprintf(w, "data: %s\n", message)
|
||||||
if index := strings.IndexAny(message, " "); index != -1 {
|
if index := strings.IndexAny(message, " "); index != -1 {
|
||||||
|
|||||||
Reference in New Issue
Block a user