about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2016-10-20 00:24:16 -0700
committerKartik K. Agaram <vc@akkartik.com>2016-10-20 00:24:16 -0700
commitf24eeaab13d12b87bb219cb42861c5fe7d091053 (patch)
tree764b500390f33783d17d21fc8709c6ded08bd935
parent6c96a437cef5140197660a0903309f11c364bf78 (diff)
downloadmu-f24eeaab13d12b87bb219cb42861c5fe7d091053.tar.gz
3523 - http client now working
-rw-r--r--091socket.cc99
-rw-r--r--092socket.mu52
-rw-r--r--http-client.mu24
3 files changed, 163 insertions, 12 deletions
diff --git a/091socket.cc b/091socket.cc
index 97c90716..74626853 100644
--- a/091socket.cc
+++ b/091socket.cc
@@ -2,13 +2,78 @@
 struct socket_t {
   int fd;
   sockaddr_in addr;
+  bool polled;
   socket_t() {
     fd = 0;
+    polled = false;
     bzero(&addr, sizeof(addr));
   }
 };
 
 :(before "End Primitive Recipe Declarations")
+_OPEN_CLIENT_SOCKET,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "$open-client-socket", _OPEN_CLIENT_SOCKET);
+:(before "End Primitive Recipe Checks")
+case _OPEN_CLIENT_SOCKET: {
+  if (SIZE(inst.ingredients) != 2) {
+    raise << maybe(get(Recipe, r).name) << "'$open-client-socket' requires exactly two ingredients, but got '" << inst.original_string << "'\n" << end();
+    break;
+  }
+  if (!is_mu_text(inst.ingredients.at(0))) {
+    raise << maybe(get(Recipe, r).name) << "first ingredient of '$open-client-socket' should be text (the hostname), but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
+    break;
+  }
+  if (!is_mu_number(inst.ingredients.at(1))) {
+    raise << maybe(get(Recipe, r).name) << "second ingredient of '$open-client-socket' should be a number (the port of the hostname to connect to), but got '" << to_string(inst.ingredients.at(1)) << "'\n" << end();
+    break;
+  }
+  if (SIZE(inst.products) != 1) {
+    raise << maybe(get(Recipe, r).name) << "'$open-client-socket' requires exactly one product, but got '" << inst.original_string << "'\n" << end();
+    break;
+  }
+  if (!is_mu_number(inst.products.at(0))) {
+    raise << maybe(get(Recipe, r).name) << "first product of '$open-client-socket' should be a number (socket handle), but got '" << to_string(inst.products.at(0)) << "'\n" << end();
+    break;
+  }
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case _OPEN_CLIENT_SOCKET: {
+  string host = read_mu_text(ingredients.at(0).at(0));
+  int port = ingredients.at(1).at(0);
+  socket_t* client = client_socket(host, port);
+  products.resize(1);
+  if (client->fd < 0) {  // error
+    delete client;
+    products.at(0).push_back(0);
+    break;
+  }
+  long long int result = reinterpret_cast<long long int>(client);
+  products.at(0).push_back(static_cast<double>(result));
+  break;
+}
+:(code)
+socket_t* client_socket(const string& host, int port) {
+  socket_t* result = new socket_t;
+  result->fd = socket(AF_INET, SOCK_STREAM, 0);
+  if (result->fd < 0) {
+    raise << "Failed to create socket.\n" << end();
+    return result;
+  }
+  result->addr.sin_family = AF_INET;
+  hostent* tmp = gethostbyname(host.c_str());
+  bcopy(tmp->h_addr, reinterpret_cast<char*>(&result->addr.sin_addr.s_addr), tmp->h_length);
+  result->addr.sin_port = htons(port);
+  if (connect(result->fd, reinterpret_cast<sockaddr*>(&result->addr), sizeof(result->addr)) < 0) {
+    close(result->fd);
+    result->fd = -1;
+    raise << "Failed to connect to " << host << ':' << port << '\n' << end();
+  }
+  return result;
+}
+
+:(before "End Primitive Recipe Declarations")
 _OPEN_SERVER_SOCKET,
 :(before "End Primitive Recipe Numbers")
 put(Recipe_ordinal, "$open-server-socket", _OPEN_SERVER_SOCKET);
@@ -154,17 +219,35 @@ case _READ_FROM_SOCKET: {
 :(before "End Primitive Recipe Implementations")
 case _READ_FROM_SOCKET: {
   long long int x = static_cast<long long int>(ingredients.at(0).at(0));
-  int bytes = static_cast<int>(ingredients.at(1).at(0));
   socket_t* socket = reinterpret_cast<socket_t*>(x);
-  int socket_fd = socket->fd;
-  char contents[bytes];
+  // 1. we'd like to simply read() from the socket
+  // however read() on a socket never returns EOF, so we wouldn't know when to stop
+  // 2. recv() can signal EOF, but it also signals "no data yet" in the beginning
+  // so use poll() in the beginning to wait for data before calling recv()
+  // 3. but poll() will block on EOF, so only use poll() on the very first
+  // $read-from-socket on a socket
+  if (!socket->polled) {
+    socket->polled = true;
+    pollfd p;
+    bzero(&p, sizeof(p));
+    p.fd = socket->fd;
+    p.events = POLLIN | POLLHUP;
+    if (poll(&p, /*num pollfds*/1, /*no timeout*/-1) <= 0) {
+      raise << maybe(current_recipe_name()) << "error in $read-from-socket\n" << end();
+      products.resize(2);
+      products.at(0).push_back(0);
+      products.at(1).push_back(false);
+      break;
+    }
+  }
+  int bytes = static_cast<int>(ingredients.at(1).at(0));
+  char* contents = new char[bytes];
   bzero(contents, bytes);
-  int bytes_read = read(socket_fd, contents, bytes-/*terminal null*/1);
-//?   cerr << "Read:\n" << string(contents) << "\n";
-//?   cerr << "bytes read: " << bytes_read << '\n';
+  int bytes_read = recv(socket->fd, contents, bytes-/*terminal null*/1, MSG_DONTWAIT);
   products.resize(2);
   products.at(0).push_back(new_mu_text(contents));
-  products.at(1).push_back(bytes_read < bytes-1);
+  products.at(1).push_back(bytes_read <= 0);
+  delete contents;
   break;
 }
 
@@ -223,5 +306,7 @@ case _CLOSE_SOCKET: {
 
 :(before "End Includes")
 #include <netinet/in.h>
+#include <netdb.h>
+#include <poll.h>
 #include <sys/socket.h>
 #include <unistd.h>
diff --git a/092socket.mu b/092socket.mu
index fed6d927..e7a57b5d 100644
--- a/092socket.mu
+++ b/092socket.mu
@@ -57,6 +57,51 @@ scenario write-to-fake-socket [
   ]
 ]
 
+def start-reading-from-network resources:&:resources, host:text, path:text -> contents:&:source:char [
+  local-scope
+  load-ingredients
+  {
+    break-if resources
+    # real network
+    socket:num <- $open-client-socket host, 80/http-port
+    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
@@ -122,10 +167,8 @@ def receive-from-socket session:num, sink:&:sink:char -> sink:&:sink:char [
   {
     req:text, eof?:bool <- $read-from-socket session, 4096/bytes
     bytes-read:num <- length *req
-#?     $print [read ], bytes-read, [ bytes from socket], 10/newline
     i:num <- copy 0
     {
-#?       $print [  write ], i, 10/newline
       done?:bool <- greater-or-equal i, bytes-read
       break-if done?
       c:char <- index *req, i  # todo: unicode
@@ -138,7 +181,7 @@ def receive-from-socket session:num, sink:&:sink:char -> sink:&:sink:char [
   sink <- close sink
 ]
 
-def write-to-socket session-socket:num, s:text [
+def write-to-socket socket:num, s:text [
   local-scope
   load-ingredients
   len:num <- length *s
@@ -147,8 +190,7 @@ def write-to-socket session-socket:num, s:text [
     done?:bool <- greater-or-equal i, len
     break-if done?
     c:char <- index *s, i
-    $print [writing to socket: ], i, [ ], c, 10/newline
-    $write-to-socket session-socket, c
+    $write-to-socket socket, c
     i <- add i, 1
     loop
   }
diff --git a/http-client.mu b/http-client.mu
new file mode 100644
index 00000000..eddbe2a4
--- /dev/null
+++ b/http-client.mu
@@ -0,0 +1,24 @@
+def main [
+  local-scope
+  google:&:source:char <- start-reading-from-network 0/real-resources, [google.com], [/]
+  n:num <- copy 0
+  b:&:buffer <- new-buffer 30
+  {
+    c:char, done?:bool <- read google
+    break-if done?
+#?     $print c, 10/newline
+    n <- add n, 1
+#?     skip?:bool <- lesser-than n, 10000
+#?     loop-if skip?
+    b <- append b, c
+#?     trunc?:bool <- greater-or-equal n, 10000
+#?     loop-unless trunc?
+    loop
+  }
+  result:text <- buffer-to-array b
+  open-console
+  len:num <- length *result
+  print 0/real-screen, result
+  wait-for-some-interaction
+  close-console
+]