Browse Source

Update to libreactor 2.0 (#6204)

* libreactor: Standardize on Ubuntu 20.04 and GCC 10

* libreactor: upgrade to v2.0

Stream code now uses recv/send instead of read/write if stream_is_socket is true. This gives a small speed boost.

Network bind code no longer needs pthread so I deleted my custom_reactor_net_bind.

The http_date functions is now visible so I am using it directly instead of reimplementing it.
Marc Richards 4 years ago
parent
commit
db3ec363d2

+ 1 - 1
frameworks/C/libreactor/Makefile

@@ -1,6 +1,6 @@
 PROG    = libreactor
 OBJS    = src/setup.o src/helpers.o src/main.o
-CFLAGS  = -std=gnu11 -Wall -Wextra -Wpedantic -O3 -g -flto
+CFLAGS  = -std=gnu11 -Wall -Wextra -Wpedantic -O3 -g -flto -fcommon
 LDADD   = -lreactor -ldynamic -lclo
 
 $(PROG): $(OBJS)

+ 2 - 2
frameworks/C/libreactor/Makefile-server

@@ -1,8 +1,8 @@
 PROG    = libreactor
 OBJS    = src/setup.o src/main.o
-CFLAGS  = -std=gnu11 -Wall -Wextra -Wpedantic -O3 -g
+CFLAGS  = -std=gnu11 -Wall -Wextra -Wpedantic -O3 -g -flto -fcommon
 LDFLAGS = -pthread
-LDADD   = -lreactor -ldynamic -lclo -flto
+LDADD   = -lreactor -ldynamic -lclo
 
 $(PROG): $(OBJS)
 	$(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) $(LDADD)

+ 20 - 18
frameworks/C/libreactor/libreactor-server.dockerfile

@@ -1,11 +1,11 @@
-FROM ubuntu:18.04 as builder
+FROM ubuntu:20.04 as builder
 
 RUN apt-get update -yqq
-RUN apt-get install -yqq wget make automake libtool file gcc-8 g++-8
+RUN apt-get install -yqq wget git make automake libtool file gcc-10 g++-10
 
-WORKDIR /libreactor
+WORKDIR /build
 
-ENV CC=gcc-8 AR=gcc-ar-8 NM=gcc-nm-8 RANLIB=gcc-ranlib-8
+ENV CC=gcc-10 AR=gcc-ar-10 NM=gcc-nm-10 RANLIB=gcc-ranlib-10
 
 RUN wget -q https://github.com/akheron/jansson/archive/v2.12.tar.gz -O jansson-2.12.tar.gz && \
     tar xfz jansson-2.12.tar.gz && \
@@ -14,33 +14,35 @@ RUN wget -q https://github.com/akheron/jansson/archive/v2.12.tar.gz -O jansson-2
     ./configure && \
     make install
 
-RUN wget -q https://github.com/fredrikwidlund/libdynamic/releases/download/v1.3.0/libdynamic-1.3.0.tar.gz && \
-    tar xfz libdynamic-1.3.0.tar.gz && \
-    cd libdynamic-1.3.0 && \
-    ./configure --prefix=/usr && \
+RUN git clone https://github.com/fredrikwidlund/libdynamic && \
+    cd libdynamic && \
+    ./autogen.sh && \
+    ./configure --prefix=/usr CFLAGS="-Wall -Wextra -Wpedantic -O3 -g"&& \
     make install
 
+# Using sed to remove the unused "#include <dynamic.h>" directive since it causes a build error: "unknown type name 'pthread_t'"
 RUN wget -q https://github.com/fredrikwidlund/libclo/releases/download/v1.0.0/libclo-1.0.0.tar.gz && \
     tar xfz libclo-1.0.0.tar.gz && \
     cd libclo-1.0.0 && \
-    ./configure && \
+    sed -i '/#include <dynamic.h>/d' ./src/clo.c && \
+    ./configure --prefix=/usr CFLAGS="-Wall -Wextra -Wpedantic -O3 -g" && \
     make install
 
-RUN wget -q https://github.com/fredrikwidlund/libreactor/releases/download/v1.0.1/libreactor-1.0.1.tar.gz && \
-    tar xfz libreactor-1.0.1.tar.gz && \
-    cd libreactor-1.0.1 && \
-    ./configure --prefix=/usr CFLAGS="-Wall -Wextra -Wpedantic -O3 -g" && \
+RUN git clone https://github.com/fredrikwidlund/libreactor --single-branch --branch release-2.0 libreactor-2 && \
+    cd libreactor-2 && \
+    ./autogen.sh && \
+    ./configure --prefix=/usr CFLAGS="-Wall -Wextra -Wpedantic -O3 -g -fcommon" && \
     make install
 
-COPY src-server/ /libreactor/src/
-COPY Makefile-server /libreactor/Makefile
+COPY src-server/ /build/src/
+COPY Makefile-server /build/Makefile
 
 RUN make
 
 
-FROM ubuntu:18.04
+FROM ubuntu:20.04
 
-WORKDIR /libreactor
-COPY --from=builder /libreactor .
+WORKDIR /app
+COPY --from=builder /build/libreactor .
 
 CMD ["./libreactor"]

+ 18 - 16
frameworks/C/libreactor/libreactor.dockerfile

@@ -1,11 +1,11 @@
 FROM ubuntu:20.04 as builder
 
 RUN apt-get update -yqq
-RUN apt-get install -yqq wget make automake libtool file gcc-9 g++-9
+RUN apt-get install -yqq wget git make automake libtool file gcc-10 g++-10
 
-WORKDIR /libreactor
+WORKDIR /build
 
-ENV CC=gcc-9 AR=gcc-ar-9 NM=gcc-nm-9 RANLIB=gcc-ranlib-9
+ENV CC=gcc-10 AR=gcc-ar-10 NM=gcc-nm-10 RANLIB=gcc-ranlib-10
 
 RUN wget -q https://github.com/akheron/jansson/archive/v2.12.tar.gz -O jansson-2.12.tar.gz && \
     tar xfz jansson-2.12.tar.gz && \
@@ -14,33 +14,35 @@ RUN wget -q https://github.com/akheron/jansson/archive/v2.12.tar.gz -O jansson-2
     ./configure && \
     make install
 
-RUN wget -q https://github.com/fredrikwidlund/libdynamic/releases/download/v1.3.0/libdynamic-1.3.0.tar.gz && \
-    tar xfz libdynamic-1.3.0.tar.gz && \
-    cd libdynamic-1.3.0 && \
-    ./configure --prefix=/usr && \
+RUN git clone https://github.com/fredrikwidlund/libdynamic && \
+    cd libdynamic && \
+    ./autogen.sh && \
+    ./configure --prefix=/usr CFLAGS="-Wall -Wextra -Wpedantic -O3 -g"&& \
     make install
 
+# Using sed to remove the unused "#include <dynamic.h>" directive since it causes a build error: "unknown type name 'pthread_t'"
 RUN wget -q https://github.com/fredrikwidlund/libclo/releases/download/v1.0.0/libclo-1.0.0.tar.gz && \
     tar xfz libclo-1.0.0.tar.gz && \
     cd libclo-1.0.0 && \
-    ./configure && \
+    sed -i '/#include <dynamic.h>/d' ./src/clo.c && \
+    ./configure --prefix=/usr CFLAGS="-Wall -Wextra -Wpedantic -O3 -g" && \
     make install
 
-RUN wget -q https://github.com/fredrikwidlund/libreactor/releases/download/v1.0.1/libreactor-1.0.1.tar.gz && \
-    tar xfz libreactor-1.0.1.tar.gz && \
-    cd libreactor-1.0.1 && \
-    ./configure --prefix=/usr CFLAGS="-Wall -Wextra -Wpedantic -O3 -g" && \
+RUN git clone https://github.com/fredrikwidlund/libreactor --single-branch --branch release-2.0 libreactor-2 && \
+    cd libreactor-2 && \
+    ./autogen.sh && \
+    ./configure --prefix=/usr CFLAGS="-Wall -Wextra -Wpedantic -O3 -g -fcommon" && \
     make install
 
-COPY src/ /libreactor/src/
-COPY Makefile /libreactor/Makefile
+COPY src/ /build/src/
+COPY Makefile /build/Makefile
 
 RUN make
 
 
 FROM ubuntu:20.04
 
-WORKDIR /libreactor
-COPY --from=builder /libreactor .
+WORKDIR /app
+COPY --from=builder /build/libreactor .
 
 CMD ["./libreactor"]

+ 27 - 22
frameworks/C/libreactor/src-server/main.c

@@ -11,37 +11,42 @@
 
 #include "setup.h"
 
-static reactor_status tfb(reactor_event *event)
+static core_status server_handler(core_event *event)
 {
-  reactor_server_session *session = (reactor_server_session *) event->data;
-
-  if (reactor_vector_equal(session->request->target, reactor_vector_string("/json"))) {
-    char json_msg[32];
-    (void) clo_encode((clo[]) {clo_object({"message", clo_string("Hello, World!")})}, json_msg, sizeof(json_msg));
-    reactor_server_ok(session, reactor_vector_string("application/json"), reactor_vector_string(json_msg));
-    return REACTOR_OK;
-  }
-  else if (reactor_vector_equal(session->request->target, reactor_vector_string("/plaintext"))) {
-    reactor_server_ok(session, reactor_vector_string("text/plain"), reactor_vector_string("Hello, World!"));
-    return REACTOR_OK;
+  static char json_msg[4096];
+
+  server *server = event->state;
+  server_context *context = (server_context *) event->data;
+
+  if (event->type == SERVER_REQUEST){
+    if (segment_equal(context->request.target, segment_string("/json"))){
+      (void) clo_encode((clo[]) {clo_object({"message", clo_string("Hello, World!")})}, json_msg, sizeof(json_msg));
+      server_ok(context, segment_string("application/json"), segment_string(json_msg));
+    }
+    else if (segment_equal(context->request.target, segment_string("/plaintext"))){
+      server_ok(context, segment_string("text/plain"), segment_string("Hello, World!"));
+    }
+    else{
+      server_ok(context, segment_string("text/plain"), segment_string("Hello from libreactor!\n"));
+    }
+    return CORE_OK;
   }
   else {
-    reactor_server_ok(session, reactor_vector_string("text/plain"), reactor_vector_string("Hello from libreactor!\n"));
-    return REACTOR_OK;
+    warn("error");
+    server_destruct(server);
+    return CORE_ABORT;
   }
 }
 
-
 int main()
 {
-  reactor_server server;
+  server s;
 
   setup();
-  reactor_construct();
-  reactor_server_construct(&server, NULL, NULL);
-  reactor_server_route(&server, tfb, NULL);
-  (void) reactor_server_open(&server, "0.0.0.0", "8080");
+  core_construct(NULL);
+  server_construct(&s, server_handler, &s);
+  server_open(&s, 0, 8080);
 
-  reactor_run();
-  reactor_destruct();
+  core_loop(NULL);
+  core_destruct(NULL);
 }

+ 18 - 93
frameworks/C/libreactor/src/helpers.c

@@ -2,127 +2,52 @@
 #include <stdint.h>
 #include <stdlib.h>
 #include <unistd.h>
-#include <netdb.h>
-#include <errno.h>
 #include <limits.h>
 #include <string.h>
-#include <time.h>
 #include <err.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/epoll.h>
 
 #include <dynamic.h>
 #include <reactor.h>
 #include <clo.h>
 
 // Returns the full header and trailing \r\n
-reactor_vector http_date_header(int update)
+segment http_date_header()
 {
-  time_t t;
-  struct tm tm;
-  static const char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
-  static const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
   static __thread char date_header[38] = "Date: Thu, 01 Jan 1970 00:00:00 GMT\r\n";
+  segment date = http_date(0);
+  memcpy(date_header + 6, date.base, date.size);
 
-  if (update)
-    {
-      (void) time(&t);
-      (void) gmtime_r(&t, &tm);
-      (void) strftime(date_header, 38, "Date: ---, %d --- %Y %H:%M:%S GMT\r\n", &tm);
-      memcpy(date_header + 6, days[tm.tm_wday], 3);
-      memcpy(date_header + 14, months[tm.tm_mon], 3);
-    }
-
-  return (reactor_vector) {date_header, 37};
+  return (segment) {date_header, 37};
 }
 
 // Returns the full header and trailing \r\n
 // Also includes the final \r\n separating the headers from response body
-reactor_vector http_content_length_header(uint32_t n)
+segment http_content_length_header(uint32_t n)
 {
-  static __thread char header[32] = "Content-Length: "; // full 32 elements still allocated
-  size_t length = reactor_utility_u32len(n);
+  // Max content length limited by uint32_t which is 4294967296 (4GB) or 10 chars when written out.
+  // 16 (header name) + 10 (header value) + 4 (newlines) + 1 (null terminator) = 31
+  static __thread char header[32] = "Content-Length: ";
+  size_t length = utility_u32_len(n);
 
-  reactor_utility_u32sprint(n, header + length + 16);
+  utility_u32_sprint(n, header + length + 16);
   memcpy(header + length + 16, "\r\n\r\n", 4);
 
-  return (reactor_vector){header, length + 16 + 4};
+  return (segment) {header, length + 16 + 4};
 }
 
-void write_response(reactor_stream *stream, reactor_vector preamble, reactor_vector body)
+void write_response(stream *stream, segment preamble, segment body)
 {
-  char *output_buffer_ptr;
-  reactor_vector date_header = http_date_header(0); // includes header name and \r\n
-  reactor_vector content_length_header = http_content_length_header(body.size); // includes header name and \r\n\r\n
+  segment date_header = http_date_header(0); // includes header name, value, and \r\n
+  segment content_length_header = http_content_length_header(body.size); // includes header name, value, and \r\n\r\n
   size_t response_size = preamble.size + date_header.size + content_length_header.size + body.size;
 
-  // Reserves additional space in the stream's output buffer (if necessary), updates the size, and returns a pointer
-  output_buffer_ptr = reactor_stream_segment(stream, response_size);
+  // Reserves additional space in the stream's output buffer (if needed) and updates the buffer size
+  // stream_allocate returns a segment which we convert to a char * pointer
+   char *output_buffer_ptr = (char *) (stream_allocate(stream, response_size)).base;
 
-  // memcpy the response directly to the output buffer
+  // memcpy the response directly to the output stream buffer
   memcpy(output_buffer_ptr, preamble.base, preamble.size);
   memcpy(output_buffer_ptr + preamble.size, date_header.base, date_header.size);
   memcpy(output_buffer_ptr + preamble.size + date_header.size, content_length_header.base, content_length_header.size);
   memcpy(output_buffer_ptr + preamble.size + date_header.size + content_length_header.size, body.base, body.size);
-}
-
-
-// Custom version of reactor_net_bind that doesn't use reactor_resolver/reactor_pool to resolve the address
-// We just call getaddrinfo here directly instead. It is called at start we don't need to worry about it blocking
-// By not using reactor_net_bind we can avoid reactor_pool altogether and compile without -pthread. This seems to
-// provide some additional performance
-reactor_status custom_reactor_net_bind(reactor_net *net, char *node, char *service)
-{
-  int e, fileno;
-  struct addrinfo *ai;
-  struct addrinfo hints;
-
-  if (reactor_fd_active(&net->fd))
-    return REACTOR_ERROR;
-
-  net->options |= REACTOR_NET_OPTION_PASSIVE;
-
-  hints = (struct addrinfo) {.ai_family = 0, .ai_socktype = SOCK_STREAM, .ai_flags = AI_PASSIVE};
-  e = getaddrinfo(node, service, &hints, &ai);
-
-  if (e)
-    err(1, "unable to resolve %s:%s %s\n", node, service, gai_strerror(e));
-
-  fileno = socket(ai->ai_family, SOCK_STREAM | SOCK_NONBLOCK, 0);
-  reactor_assert_int_not_equal(fileno, -1);
-
-  if (net->options & REACTOR_NET_OPTION_PASSIVE)
-    {
-      if (net->options & REACTOR_NET_OPTION_REUSEPORT)
-        (void) setsockopt(fileno, SOL_SOCKET, SO_REUSEPORT, (int[]){1}, sizeof(int));
-      if (net->options & REACTOR_NET_OPTION_REUSEADDR)
-        (void) setsockopt(fileno, SOL_SOCKET, SO_REUSEADDR, (int[]){1}, sizeof(int));
-
-      e = bind(fileno, ai->ai_addr, ai->ai_addrlen);
-      if (e == -1)
-        {
-          (void) close(fileno);
-          err(1, "error binding socket");
-        }
-
-      e = listen(fileno, INT_MAX);
-      reactor_assert_int_not_equal(e, -1);
-      reactor_fd_open(&net->fd, fileno, EPOLLIN | EPOLLET);
-    }
-  else
-    {
-     e = connect(fileno, ai->ai_addr, ai->ai_addrlen);
-      if (e == -1 && errno != EINPROGRESS)
-        {
-          (void) close(fileno);
-          err(1, "error connecting socket");
-        }
-
-      reactor_fd_open(&net->fd, fileno, EPOLLOUT | EPOLLET);
-    }
-
-  freeaddrinfo(ai);
-
-  return REACTOR_OK;
 }

+ 3 - 5
frameworks/C/libreactor/src/helpers.h

@@ -1,12 +1,10 @@
 #ifndef HELPERS_H_INCLUDED
 #define HELPERS_H_INCLUDED
 
-reactor_vector http_date_header(int update);
+segment http_date_header(int update);
 
-reactor_vector http_content_length_header(uint32_t n);
+segment http_content_length_header(uint32_t n);
 
-void write_response(reactor_stream *stream, reactor_vector preamble, reactor_vector body);
-
-reactor_status custom_reactor_net_bind(reactor_net *net, char *node, char *service);
+void write_response(stream *stream, segment preamble, segment body);
 
 #endif /* HELPERS_H_INCLUDED */

+ 25 - 68
frameworks/C/libreactor/src/main.c

@@ -21,102 +21,59 @@
                       "Server: libreactor\r\n"\
                       "Content-Type: text/plain\r\n"
 
-struct http_server
+void plaintext(server_context *context, char *response)
 {
-  reactor_net    net;
-  list           connections;
-};
-
-static void plaintext(reactor_stream *stream, char *response)
-{
-  static const reactor_vector text_preamble = { .base = TEXT_PREAMBLE, .size = sizeof(TEXT_PREAMBLE) - 1 };
-  write_response(stream, text_preamble, reactor_vector_string(response));
+  static const segment text_preamble = { .base = TEXT_PREAMBLE, .size = sizeof(TEXT_PREAMBLE) - 1 };
+  write_response(&context->session->stream, text_preamble, segment_string(response));
 }
 
-static void json(reactor_stream *stream, clo *json_object)
+void json(server_context *context, clo *json_object)
 {
-  static const reactor_vector json_preamble = { .base = JSON_PREAMBLE, .size = sizeof(JSON_PREAMBLE) - 1 };
+  static const segment json_preamble = { .base = JSON_PREAMBLE, .size = sizeof(JSON_PREAMBLE) - 1 };
   static char json_string[4096];
 
   (void) clo_encode(json_object, json_string, sizeof(json_string));
-  write_response(stream, json_preamble, reactor_vector_string(json_string));
+  write_response(&context->session->stream, json_preamble, segment_string(json_string));
 }
 
-static reactor_status http_handler(reactor_event *event)
+static core_status server_handler(core_event *event)
 {
   static char hello_string[] = "Hello, World!";
   static char default_string[] = "Hello from libreactor!\n";
   static clo_pair json_pair[] = {{ .string = "message", .value = { .type = CLO_STRING, .string = "Hello, World!" }}};
   static clo json_object[] = {{ .type = CLO_OBJECT, .object = json_pair }};
 
-  reactor_http *http_connection = event->state;
-  reactor_http_request *request;
+  server *server = event->state;
+  server_context *context = (server_context *) event->data;
 
-  if (event->type == REACTOR_HTTP_EVENT_REQUEST){
-    request = (reactor_http_request *) event->data;
-
-    if (reactor_vector_equal(request->target, reactor_vector_string("/json"))){
-      json(&http_connection->stream, json_object);
+  if (event->type == SERVER_REQUEST){
+    if (segment_equal(context->request.target, segment_string("/json"))){
+      json(context, json_object);
     }
-    else if (reactor_vector_equal(request->target, reactor_vector_string("/plaintext"))){
-      plaintext(&http_connection->stream, hello_string);
+    else if (segment_equal(context->request.target, segment_string("/plaintext"))){
+      plaintext(context, hello_string);
     }
     else{
-      plaintext(&http_connection->stream, default_string);
+      plaintext(context, default_string);
     }
-    return REACTOR_OK;
+    return CORE_OK;
   }
   else {
-    reactor_http_destruct(http_connection);
-    list_erase(http_connection, NULL);
-    return REACTOR_ABORT;
+    warn("error");
+    server_destruct(server);
+    return CORE_ABORT;
   }
 }
 
-static reactor_status net_handler(reactor_event *event)
-{
-  struct http_server *server = event->state;
-  reactor_http connection_initializer = (reactor_http) {0};
-  reactor_http *http_connection;
-
-  switch (event->type)
-    {
-    case REACTOR_NET_EVENT_ACCEPT:
-      http_connection = list_push_back(&server->connections, &connection_initializer, sizeof (reactor_http));
-      reactor_http_construct(http_connection, http_handler, http_connection);
-      reactor_http_set_mode(http_connection, REACTOR_HTTP_MODE_REQUEST);
-      reactor_http_open(http_connection, event->data);
-      return REACTOR_OK;
-    default:
-      reactor_net_destruct(&server->net);
-      return REACTOR_ABORT;
-    }
-}
-
-static reactor_status http_date_timer_handler(reactor_event *event)
-{
-  (void) event;
-  http_date_header(1); // update the date header
-  return REACTOR_OK;
-}
-
 int main()
 {
-  struct http_server server = {0};
-  reactor_timer timer;
+  server s;
 
   setup();
-  list_construct(&server.connections);
-  reactor_core_construct();
-
-  // Set the correct date before the server starts. Timer then updates it every second
-  http_date_header(1);
-  reactor_timer_construct(&timer, http_date_timer_handler, &timer);
-  reactor_timer_set(&timer, 1, 1000000000);
-
-  reactor_net_construct(&server.net, net_handler, &server);
-  (void) custom_reactor_net_bind(&server.net, "0.0.0.0", "8080");
+  core_construct(NULL);
+  server_construct(&s, server_handler, &s);
+  server_open(&s, 0, 8080);
 
-  reactor_core_run();
-  reactor_core_destruct();
+  core_loop(NULL);
+  core_destruct(NULL);
 }