about summary refs log blame commit diff stats
path: root/092socket.mu
blob: 1fd2586b6f719fa44f18db05e1c259f00a7bcb32 (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                            
 


                                                                        
 
                                                                      
 
                              
             



















                                                                                             
 

                                                 

                  
             

 

















                                                                                       
             










                                                            

 


                                                                                                      





                                                         


                      

                                                





































                                                                 













                                                                                                   
 










































                                                                                                                                         
 
                                                                          


                  
                                                  
                                                               
                   
                                 



                                                  
                                              



                           
                    



                    
                                        







                                         
                              



                 
# Wrappers around socket primitives that are easier to test.
#
# To test client operations, use `assume-resources` with a filename that
# begins with a hostname. (Filenames starting with '/' are assumed to be
# local.)
#
# To test server operations, just run a real client against localhost.

scenario example-server-test [
  local-scope
  # test server without a fake on a random (real) port
  # that way repeatedly running the test will give ports time to timeout and
  # close before reusing them
  make-random-nondeterministic
  port:num <- random-in-range 0/real-random-numbers, 8000, 8100
  run [
    socket:num <- $open-server-socket port
    $print [server socket: ], socket, 10/newline
    assert socket, [ 
F - example-server-test: $open-server-socket failed]
    $print [starting up server routine], 10/newline
    handler-routine:number <- start-running serve-one-request socket, example-handler
  ]
  $print [starting to read from port ], port, 10/newline
  source:&:source:char <- start-reading-from-network 0/real-resources, [localhost], [/], port
  response:text <- drain source
  10:@:char/raw <- copy *response
  memory-should-contain [
    10:array:character <- [abc]
  ]
]
# helper just for this scenario
def example-handler query:text -> response:text [
  local-scope
  load-ingredients
  reply [abc]
]

#? scenario example-client-test [
#?   local-scope
#?   assume-resources [
#?     [example.com/] -> [abc]
#?   ]
#?   run [
#?     source:&:source:char <- start-reading-from-network resources, [example.com], [/]
#?   ]
#?   contents:text <- drain source
#?   10:@:char/raw <- copy *contents
#?   memory-should-contain [
#?     10:address:character <- [abc]
#?   ]
#? ]

type request-handler = (recipe text -> text)

def serve-one-request socket:num, request-handler:request-handler [
  local-scope
  load-ingredients
  session:num <- $accept socket
  $print [server session socket: ], session, 10/newline
  assert session, [ 
F - example-server-test: $accept failed]
  contents:&:source:char, sink:&:sink:char <- new-channel 30
  sink <- start-running receive-from-socket session, sink
  query:text <- drain contents
  response:text <- call request-handler, query
  write-to-socket session, response
  $close-socket session
]

def start-reading-from-network resources:&:resources, host:text, path:text -> contents:&:source:char [
  local-scope
  load-ingredients
  $print [running start-reading-from-network], 10/newline
  {
    port:num, port-found?:boolean <- next-ingredient
    break-if port-found?
    port <- copy 80/http-port
  }
  {
    break-if resources
    # real network
    socket:num <- $open-client-socket host, port
    $print [client socket: ], socket, 10/newline
    assert socket, [contents]
    req:text <- interpolate [GET _ HTTP/1.1], path
    request-socket socket, req
    contents:&:source:char, sink:&:sink:char <- new-channel 10000
    start-running receive-from-socket socket, sink
    return
  }
  # fake network
#?   i:num <- copy 0
#?   data:&:@:resource <- get *fs, data:offset
#?   len:num <- length *data
#?   {
#?     done?:bool <- greater-or-equal i, len
#?     break-if done?
#?     tmp:resource <- index *data, i
#?     i <- add i, 1
#?     curr-filename:text <- get tmp, name:offset
#?     found?:bool <- equal filename, curr-filename
#?     loop-unless found?
#?     contents:&:source:char, sink:&:sink:char <- new-channel 30
#?     curr-contents:text <- get tmp, contents:offset
#?     start-running transmit-from-text curr-contents, sink
#?     return
#?   }
  return 0/not-found
]

def request-socket socket:num, s:text -> socket:num [
  local-scope
  load-ingredients
  write-to-socket socket, s
  $write-to-socket socket, 13/cr
  $write-to-socket socket, 10/lf
  # empty line to delimit request
  $write-to-socket socket, 13/cr
  $write-to-socket socket, 10/lf
]

#? def start-writing-socket network:&:local-network, port:num -> sink:&:sink:char, routine-id:num [
#?   local-scope
#?   load-ingredients
#?   source:&:source:char, sink:&:sink:char <- new-channel 30
#?   {
#?     break-if network
#?     socket:num <- $open-server-socket port
#?     session:num <- $accept socket
#?     # TODO Create channel implementation of write-to-socket.
#?     return sink, 0/routine-id
#?   }
#?   # fake network
#?   routine-id <- start-running transmit-to-fake-socket network, port, source
#? ]

#? def transmit-to-fake-socket network:&:local-network, port:num, source:&:source:char -> network:&:local-network, source:&:source:char [
#?   local-scope
#?   load-ingredients
#?   # compute new port connection contents
#?   buf:&:buffer <- new-buffer 30
#?   {
#?     c:char, done?:bool, source <- read source
#?     break-unless c
#?     buf <- append buf, c
#?     break-if done?
#?     loop
#?   }
#?   contents:text <- buffer-to-array buf
#?   new-port-connection:&:port-connection <- new-port-connection port, contents
#?   # Got the contents of the channel, time to write to fake port.
#?   i:num <- copy 0
#?   port-connections:&:@:port-connection <- get *network, data:offset
#?   len:num <- length *port-connections
#?   {
#?     done?:bool <- greater-or-equal i, len
#?     break-if done?
#?     current:port-connection <- index *port-connections, i
#?     current-port:num <- get current, port:offset
#?     ports-match?:bool <- equal current-port, port
#?     i <- add i, 1
#?     loop-unless ports-match?
#?     # Found an existing connection on this port, overwrite.
#?     put-index *port-connections, i, *new-port-connection
#?     reply
#?   }
#?   # Couldn't find an existing connection on this port, initialize a new one.
#?   new-len:num <- add len, 1
#?   new-port-connections:&:@:port-connection <- new port-connection:type, new-len
#?   put *network, data:offset, new-port-connections
#?   i:num <- copy 0
#?   {
#?     done?:bool <- greater-or-equal i, len
#?     break-if done?
#?     tmp:port-connection <- index *port-connections, i
#?     put-index *new-port-connections, i, tmp
#?   }
#?   put-index *new-port-connections, len, *new-port-connection
#? ]

def receive-from-socket socket:num, sink:&:sink:char -> sink:&:sink:char [
  local-scope
  load-ingredients
  {
    $print [read-from-socket ], socket, 10/newline
    req:text, eof?:bool <- $read-from-socket socket, 4096/bytes
    loop-unless req
    bytes-read:num <- length *req
    i:num <- copy 0
    {
      done?:bool <- greater-or-equal i, bytes-read
      break-if done?
      c:char <- index *req, i  # todo: unicode
      sink <- write sink, c
      i <- add i, 1
      loop
    }
    loop-unless eof?
  }
  sink <- close sink
]

def write-to-socket socket:num, s:text [
  local-scope
  load-ingredients
  len:num <- length *s
  i:num <- copy 0
  {
    done?:bool <- greater-or-equal i, len
    break-if done?
    c:char <- index *s, i
    $write-to-socket socket, c
    i <- add i, 1
    loop
  }
]