diff options
author | Kartik K. Agaram <vc@akkartik.com> | 2016-10-20 00:24:16 -0700 |
---|---|---|
committer | Kartik K. Agaram <vc@akkartik.com> | 2016-10-20 00:24:16 -0700 |
commit | f24eeaab13d12b87bb219cb42861c5fe7d091053 (patch) | |
tree | 764b500390f33783d17d21fc8709c6ded08bd935 | |
parent | 6c96a437cef5140197660a0903309f11c364bf78 (diff) | |
download | mu-f24eeaab13d12b87bb219cb42861c5fe7d091053.tar.gz |
3523 - http client now working
-rw-r--r-- | 091socket.cc | 99 | ||||
-rw-r--r-- | 092socket.mu | 52 | ||||
-rw-r--r-- | http-client.mu | 24 |
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 +] |