Show stdout/stderr from Wasm in terminal (#472)

This commit is contained in:
omochimetaru 2024-05-25 09:06:24 +09:00 committed by GitHub
parent 3cb3877d98
commit 6cc68d3ba8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 132 additions and 48 deletions

View File

@ -238,9 +238,14 @@ public actor Server {
let environment =
head.headers["User-Agent"].compactMap(DestinationEnvironment.init).first
?? .other
let handler = await ServerWebSocketHandler(
let handler = ServerWebSocketHandler(
configuration: ServerWebSocketHandler.Configuration(
onText: self.createWebSocketTextHandler(in: environment, terminal: self.configuration.terminal)
onText: { [weak self] (text) in
self?.webSocketTextHandler(text: text, environment: environment)
},
onBinary: { [weak self] (data) in
self?.webSocketBinaryHandler(data: data)
}
)
)
await self.add(connection: Connection(channel: channel))
@ -329,13 +334,11 @@ public actor Server {
}
extension Server {
/// Returns a handler that responds to WebSocket messages coming from the browser.
func createWebSocketTextHandler(
in environment: DestinationEnvironment,
terminal: InteractiveWriter
) -> @Sendable (String) -> Void {
{ [weak self] text in
guard let self = self else { return }
/// Respond to WebSocket messages coming from the browser.
nonisolated func webSocketTextHandler(
text: String,
environment: DestinationEnvironment
) {
guard
let data = text.data(using: .utf8),
let event = try? self.decoder.decode(Event.self, from: data)
@ -343,6 +346,8 @@ extension Server {
return
}
let terminal = self.configuration.terminal
switch event {
case let .stackTrace(rawStackTrace):
if let stackTrace = rawStackTrace.parsedStackTrace(in: environment) {
@ -374,6 +379,44 @@ extension Server {
Task { await self.stopTest(hadError: true) }
}
}
private static func decodeLines(data: Data) -> [String] {
let text = String(decoding: data, as: UTF8.self)
return text.components(separatedBy: .newlines)
}
nonisolated func webSocketBinaryHandler(data: Data) {
let terminal = self.configuration.terminal
if data.count < 2 {
return
}
var kind: UInt16 = 0
_ = withUnsafeMutableBytes(of: &kind) { (buffer) in
data.copyBytes(to: buffer, from: 0..<2)
}
kind = UInt16(littleEndian: kind)
switch kind {
case 1001:
// stdout
let chunk = data.subdata(in: 2..<data.count)
if chunk.isEmpty { return }
for line in Self.decodeLines(data: chunk) {
terminal.write("stdout: " + line + "\n")
}
case 1002:
// stderr
let chunk = data.subdata(in: 2..<data.count)
if chunk.isEmpty { return }
for line in Self.decodeLines(data: chunk) {
terminal.write("stderr: " + line + "\n", inColor: .red)
}
default: break
}
}
}

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import Foundation
import NIO
import NIOWebSocket
@ -20,7 +21,8 @@ final class ServerWebSocketHandler: ChannelInboundHandler {
typealias OutboundOut = WebSocketFrame
struct Configuration {
let onText: @Sendable (String) -> Void
var onText: @Sendable (String) -> Void
var onBinary: @Sendable (Data) -> Void
}
private var awaitingClose: Bool = false
@ -43,7 +45,11 @@ final class ServerWebSocketHandler: ChannelInboundHandler {
var data = frame.unmaskedData
let text = data.readString(length: data.readableBytes) ?? ""
self.configuration.onText(text)
case .binary, .continuation, .pong:
case .binary:
let nioData = frame.unmaskedData
let data = Data(nioData.readableBytesView)
self.configuration.onBinary(data)
case .continuation, .pong:
// We ignore these frames.
break
default:

File diff suppressed because one or more lines are too long

View File

@ -1 +1,16 @@
print("hello dev server")
#if os(WASI)
import WASILibc
typealias FILEPointer = OpaquePointer
#else
import Darwin
typealias FILEPointer = UnsafeMutablePointer<FILE>
#endif
func fputs(_ string: String, file: FILEPointer) {
_ = string.withCString { (cstr) in
fputs(cstr, file)
}
}
fputs("hello stdout\n", file: stdout)
fputs("hello stderr\n", file: stderr)

View File

@ -44,9 +44,29 @@ const startWasiTask = async () => {
const wasmRunner = WasmRunner(
{
onStdout(chunk) {
const kindBuffer = new ArrayBuffer(2);
new DataView(kindBuffer).setUint16(0, 1001, true);
const buffer = new Uint8Array(2 + chunk.length);
buffer.set(new Uint8Array(kindBuffer), 0);
buffer.set(chunk, 2);
socket.send(buffer);
},
onStdoutLine(line) {
console.log(line);
},
onStderr(chunk) {
const kindBuffer = new ArrayBuffer(2);
new DataView(kindBuffer).setUint16(0, 1002, true);
const buffer = new Uint8Array(2 + chunk.length);
buffer.set(new Uint8Array(kindBuffer), 0);
buffer.set(chunk, 2);
socket.send(buffer);
},
onStderrLine(line) {
console.error(line);
}