Bladeren bron

add example for MHD upgrade use

Christian Grothoff 9 jaren geleden
bovenliggende
commit
ca582a0633
3 gewijzigde bestanden met toevoegingen van 296 en 1 verwijderingen
  1. 6 0
      src/examples/Makefile.am
  2. 289 0
      src/examples/upgrade_example.c
  3. 1 1
      src/microhttpd/test_upgrade_common.c

+ 6 - 0
src/examples/Makefile.am

@@ -20,6 +20,7 @@ noinst_PROGRAMS = \
  benchmark_https \
  chunked_example \
  minimal_example \
+ upgrade_example \
  dual_stack_example \
  minimal_example_comet \
  querystring_example \
@@ -62,6 +63,11 @@ minimal_example_SOURCES = \
 minimal_example_LDADD = \
  $(top_builddir)/src/microhttpd/libmicrohttpd.la
 
+upgrade_example_SOURCES = \
+ upgrade_example.c
+upgrade_example_LDADD = \
+ $(top_builddir)/src/microhttpd/libmicrohttpd.la
+
 timeout_SOURCES = \
  timeout.c
 timeout_LDADD = \

+ 289 - 0
src/examples/upgrade_example.c

@@ -0,0 +1,289 @@
+/*
+     This file is part of libmicrohttpd
+     Copyright (C) 2016 Christian Grothoff (and other contributing authors)
+
+     This library is free software; you can redistribute it and/or
+     modify it under the terms of the GNU Lesser General Public
+     License as published by the Free Software Foundation; either
+     version 2.1 of the License, or (at your option) any later version.
+
+     This library is distributed in the hope that it will be useful,
+     but WITHOUT ANY WARRANTY; without even the implied warranty of
+     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+     Lesser General Public License for more details.
+
+     You should have received a copy of the GNU Lesser General Public
+     License along with this library; if not, write to the Free Software
+     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+/**
+ * @file upgrade_example.c
+ * @brief example for how to use libmicrohttpd upgrade
+ * @author Christian Grothoff
+ *
+ * Telnet to the HTTP server, use this in the request:
+ * GET / http/1.1
+ * Connection: Upgrade
+ *
+ * After this, whatever you type will be echo'ed back to you.
+ */
+
+#include "platform.h"
+#include <microhttpd.h>
+#include <pthread.h>
+
+#define PAGE "<html><head><title>libmicrohttpd demo</title></head><body>libmicrohttpd demo</body></html>"
+
+
+/**
+ * Change socket to blocking.
+ *
+ * @param fd the socket to manipulate
+ * @return non-zero if succeeded, zero otherwise
+ */
+static void
+make_blocking (MHD_socket fd)
+{
+#if defined(MHD_POSIX_SOCKETS)
+  int flags;
+
+  flags = fcntl (fd, F_GETFL);
+  if (-1 == flags)
+    return;
+  if ((flags & ~O_NONBLOCK) != flags)
+    if (-1 == fcntl (fd, F_SETFL, flags & ~O_NONBLOCK))
+      abort ();
+#elif defined(MHD_WINSOCK_SOCKETS)
+  unsigned long flags = 1;
+
+  ioctlsocket (fd, FIONBIO, &flags);
+#endif /* MHD_WINSOCK_SOCKETS */
+
+}
+
+
+static void
+send_all (MHD_socket sock,
+          const char *buf,
+          size_t len)
+{
+  ssize_t ret;
+
+  make_blocking (sock);
+  for (size_t off = 0; off < len; off += ret)
+    {
+      ret = send (sock,
+                  &buf[off],
+                  len - off,
+                  0);
+      if (0 > ret)
+        {
+          if (EAGAIN == errno)
+            {
+              ret = 0;
+              continue;
+            }
+          break;
+        }
+      if (0 == ret)
+        break;
+    }
+}
+
+
+struct MyData
+{
+  struct MHD_UpgradeResponseHandle *urh;
+  char *extra_in;
+  size_t extra_in_size;
+  MHD_socket sock;
+};
+
+
+/**
+ * Main function for the thread that runs the interaction with
+ * the upgraded socket.  Writes what it reads.
+ *
+ * @param cls the `struct MyData`
+ */
+static void *
+run_usock (void *cls)
+{
+  struct MyData *md = cls;
+  struct MHD_UpgradeResponseHandle *urh = md->urh;
+  char buf[128];
+  ssize_t got;
+
+  make_blocking (md->sock);
+  /* start by sending extra data MHD may have already read, if any */
+  if (0 != md->extra_in_size)
+    {
+      send_all (md->sock,
+                md->extra_in,
+                md->extra_in_size);
+      free (md->extra_in);
+    }
+  /* now echo in a loop */
+  while (1)
+    {
+      got = recv (md->sock,
+                  buf,
+                  sizeof (buf),
+                  0);
+      if (0 >= got)
+        break;
+      send_all (md->sock,
+                buf,
+                got);
+    }
+  free (md);
+  MHD_upgrade_action (urh,
+                      MHD_UPGRADE_ACTION_CLOSE);
+  return NULL;
+}
+
+
+/**
+ * Function called after a protocol "upgrade" response was sent
+ * successfully and the socket should now be controlled by some
+ * protocol other than HTTP.
+ *
+ * Any data already received on the socket will be made available in
+ * @e extra_in.  This can happen if the application sent extra data
+ * before MHD send the upgrade response.  The application should
+ * treat data from @a extra_in as if it had read it from the socket.
+ *
+ * Note that the application must not close() @a sock directly,
+ * but instead use #MHD_upgrade_action() for special operations
+ * on @a sock.
+ *
+ * Except when in 'thread-per-connection' mode, implementations
+ * of this function should never block (as it will still be called
+ * from within the main event loop).
+ *
+ * @param cls closure, whatever was given to #MHD_create_response_for_upgrade().
+ * @param connection original HTTP connection handle,
+ *                   giving the function a last chance
+ *                   to inspect the original HTTP request
+ * @param con_cls last value left in `con_cls` of the `MHD_AccessHandlerCallback`
+ * @param extra_in if we happened to have read bytes after the
+ *                 HTTP header already (because the client sent
+ *                 more than the HTTP header of the request before
+ *                 we sent the upgrade response),
+ *                 these are the extra bytes already read from @a sock
+ *                 by MHD.  The application should treat these as if
+ *                 it had read them from @a sock.
+ * @param extra_in_size number of bytes in @a extra_in
+ * @param sock socket to use for bi-directional communication
+ *        with the client.  For HTTPS, this may not be a socket
+ *        that is directly connected to the client and thus certain
+ *        operations (TCP-specific setsockopt(), getsockopt(), etc.)
+ *        may not work as expected (as the socket could be from a
+ *        socketpair() or a TCP-loopback).  The application is expected
+ *        to perform read()/recv() and write()/send() calls on the socket.
+ *        The application may also call shutdown(), but must not call
+ *        close() directly.
+ * @param urh argument for #MHD_upgrade_action()s on this @a connection.
+ *        Applications must eventually use this callback to (indirectly)
+ *        perform the close() action on the @a sock.
+ */
+static void
+uh_cb (void *cls,
+       struct MHD_Connection *connection,
+       void *con_cls,
+       const char *extra_in,
+       size_t extra_in_size,
+       MHD_socket sock,
+       struct MHD_UpgradeResponseHandle *urh)
+{
+  struct MyData *md;
+  pthread_t pt;
+
+  md = malloc (sizeof (struct MyData));
+  if (NULL == md)
+    abort ();
+  memset (md, 0, sizeof (struct MyData));
+  if (0 != extra_in_size)
+    {
+      md->extra_in = malloc (extra_in_size);
+      if (NULL == md->extra_in)
+        abort ();
+      memcpy (md->extra_in,
+              extra_in,
+              extra_in_size);
+    }
+  md->extra_in_size = extra_in_size;
+  md->sock = sock;
+  md->urh = urh;
+  if (0 != pthread_create (&pt,
+                           NULL,
+                           &run_usock,
+                           md))
+    abort ();
+  /* Note that by detaching like this we make it impossible to ensure
+     a clean shutdown, as the we stop the daemon even if a worker thread
+     is still running. Alas, this is a simple example... */
+  pthread_detach (pt);
+}
+
+
+static int
+ahc_echo (void *cls,
+          struct MHD_Connection *connection,
+          const char *url,
+          const char *method,
+          const char *version,
+          const char *upload_data,
+          size_t *upload_data_size,
+          void **ptr)
+{
+  static int aptr;
+  struct MHD_Response *response;
+  int ret;
+
+  if (0 != strcmp (method, "GET"))
+    return MHD_NO;              /* unexpected method */
+  if (&aptr != *ptr)
+    {
+      /* do never respond on first call */
+      *ptr = &aptr;
+      return MHD_YES;
+    }
+  *ptr = NULL;                  /* reset when done */
+  response = MHD_create_response_for_upgrade (&uh_cb,
+                                              NULL);
+
+  MHD_add_response_header (response,
+                           MHD_HTTP_HEADER_UPGRADE,
+                           "Echo Server");
+  ret = MHD_queue_response (connection,
+                            MHD_HTTP_SWITCHING_PROTOCOLS,
+                            response);
+  MHD_destroy_response (response);
+  return ret;
+}
+
+
+int
+main (int argc,
+      char *const *argv)
+{
+  struct MHD_Daemon *d;
+
+  if (argc != 2)
+    {
+      printf ("%s PORT\n", argv[0]);
+      return 1;
+    }
+  d = MHD_start_daemon (MHD_ALLOW_UPGRADE | MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+                        atoi (argv[1]),
+                        NULL, NULL,
+                        &ahc_echo, NULL,
+			MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 120,
+			MHD_OPTION_END);
+  if (d == NULL)
+    return 1;
+  (void) getc (stdin);
+  MHD_stop_daemon (d);
+  return 0;
+}

+ 1 - 1
src/microhttpd/test_upgrade_common.c

@@ -131,7 +131,7 @@ notify_connection_cb (void *cls,
 
 
 /**
- * Change socket to non-blocking.
+ * Change socket to blocking.
  *
  * @param fd the socket to manipulate
  * @return non-zero if succeeded, zero otherwise