
Loopback.pudu
Pudu105 lines4.0 KB
1/** @Test.Support.Loopback.Module — servers on the loopback interface that suites talk to */2module Support.Loopback34import Std.Bytes as ByteSeq5import Std.Concurrent as Concurrent6import Std.Http.Server.Route as Route7import Std.Http.Server as Server8import Std.Net as Net9import Std.Result as Result10import Std.Sync as Sync11import Std.Text as Text1213/** @Test.Support.Loopback.Routed — a running server and the address it answers on */14export type Routed = { running: Server.Running, base: Str }1516/** @Test.Support.Loopback.Scripted — a raw server answering connections from a script */17export type Scripted = { listener: Net.Listener, base: Str, accepted: Sync.Counter }181920export fn serve(routes: &Array[Route.Route]) -> Routed {21 let server = Server.server(&Route.routing(routes))22 let running = match Server.start("127.0.0.1", 0) {23 case Ok(found) => found24 case Err(problem) => panic("loopback server did not start: " + show(problem))25 }26 let _served = Concurrent.start(fn() -> () { let _ran = Server.run(&server, &running, 0) })27 Routed{running: running, base: "http://127.0.0.1:" + show(Server.portOf(&running))}28}293031export fn stop(routed: &Routed) -> () { let _stopped = Server.stop(&routed.running) }32333435export fn scripted(script: fn(Net.Connection, Int) -> ()) -> Scripted {36 let listener = match Net.listenLocal(0) {37 case Ok(found) => found38 case Err(problem) => panic("loopback listener did not start: " + show(problem))39 }40 let port = Result.unwrapOr(Net.portOf(&listener), 0)41 let accepted = Sync.counter(0)42 let made = Scripted{listener: listener, base: "http://127.0.0.1:" + show(port), accepted: accepted}43 let _accepting = Concurrent.start(fn() -> () {44 var going = true45 while going {46 match Net.accept(&listener) {47 case Ok(connection) => {48 let number = Result.unwrapOr(Sync.increment(&accepted, 1), 0)49 let _handled = Concurrent.start(fn() -> () { script(connection, number) })50 }51 case Err(_) => { going = false }52 }53 }54 })55 made56}575859export fn connections(server: &Scripted) -> Int { Result.unwrapOr(Sync.count(&server.accepted), 0) }606162export fn close(server: &Scripted) -> () { let _closed = Net.closeListener(&server.listener) }636465export fn readRequest(connection: &Net.Connection) -> Str {66 var pending = ByteSeq.empty()67 loop {68 let end = pending.indexOf("\r\n\r\n".toBytes())69 if end >= 0 {70 let head = Result.unwrapOr(ByteSeq.toText(&pending.take(end + 4)), "")71 let length = lengthOf(head)72 while pending.length() < end + 4 + length {73 match Net.receiveWithin(connection, 65536, 5000) {74 case Ok(Some(piece)) => { pending = pending.concat(piece) }75 case _ => { return Result.unwrapOr(ByteSeq.toText(&pending), "") }76 }77 }78 return Result.unwrapOr(ByteSeq.toText(&pending.take(end + 4 + length)), "")79 }80 match Net.receiveWithin(connection, 65536, 5000) {81 case Ok(Some(piece)) => { pending = pending.concat(piece) }82 case _ => { return Result.unwrapOr(ByteSeq.toText(&pending), "") }83 }84 }85}868788export fn write(connection: &Net.Connection, text: Str) -> () { let _sent = Net.sendText(connection, text) }899091export fn hangUp(connection: &Net.Connection) -> () { let _closed = Net.close(connection) }929394fn lengthOf(head: Str) -> Int {95 for line in head.split("\r\n") {96 if line.toLower().startsWith("content-length:") {97 return match Text.stripPrefix(line.toLower(), "content-length:").trim().toInt() {98 case Some(found) => found99 case None => 0100 }101 }102 }103 0104}105