Show stdout/stderr from Wasm in terminal (#472)
This commit is contained in:
parent
3cb3877d98
commit
6cc68d3ba8
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue