View Issue Details

IDProjectCategoryView StatusLast Update
0002102libmicrohttpdexternal APIpublic2016-10-17 19:21
ReporterChristian Grothoff Assigned ToChristian Grothoff  
PriorityhighSeverityfeatureReproducibilityN/A
Status closedResolutionfixed 
Product Version0.9.18 
Target Version0.9.52Fixed in Version0.9.52 
Summary0002102: support for WebSockets is desired
DescriptionPreliminary API was proposed on the mailinglist (under review).

The implementation can then be tested using
http://www.tavendo.de/autobahn/home.html
TagsNo tags attached.
Attached Files
mhd_upgrade.patch (5,620 bytes)   
Index: src/daemon/connection.c
===================================================================
--- src/daemon/connection.c	(revision 23229)
+++ src/daemon/connection.c	(working copy)
@@ -2046,6 +2046,7 @@
   unsigned int timeout;
   const char *end;
   int rend;
+  int upgraded = MHD_NO;
   char *line;
 
   while (1)
@@ -2320,6 +2321,16 @@
             MHD_get_response_header (connection->response, 
 				     MHD_HTTP_HEADER_CONNECTION);
 	  rend = ( (end != NULL) && (0 == strcasecmp (end, "close")) );
+
+	  if (connection->response->upgrade != NULL)
+            {
+              upgraded =
+                connection->response->upgrade (connection->response->upgrade_cls,
+        	                               connection,
+        	                               &connection->client_context,
+        	                               connection->socket_fd);
+            }
+
           MHD_destroy_response (connection->response);
           connection->response = NULL;
           if (connection->daemon->notify_completed != NULL)
@@ -2350,6 +2361,17 @@
               connection->read_closed = MHD_YES;
               connection->read_buffer_offset = 0;
             }
+          if (upgraded == MHD_YES)
+            {
+              connection->state = MHD_CONNECTION_CLOSED;
+              connection->socket_fd = -1;
+              MHD_pool_destroy (connection->pool);
+              connection->pool = NULL;
+              connection->read_buffer = NULL;
+              connection->read_buffer_size = 0;
+              connection->read_buffer_offset = 0;
+              continue;
+            }
           if (((MHD_YES == connection->read_closed) &&
                (0 == connection->read_buffer_offset)) ||
               (connection->version == NULL) ||
Index: src/daemon/daemon.c
===================================================================
--- src/daemon/daemon.c	(revision 23229)
+++ src/daemon/daemon.c	(working copy)
@@ -882,7 +882,7 @@
 
 #if HAVE_MESSAGES
 #if DEBUG_CONNECT
-  MHD_DLOG (daemon, "Accepted connection on socket %d\n", s);
+  MHD_DLOG (daemon, "Accepted connection on socket %d\n", client_socket);
 #endif
 #endif
   if ( (0 == daemon->max_connections) ||
Index: src/daemon/internal.h
===================================================================
--- src/daemon/internal.h	(revision 23229)
+++ src/daemon/internal.h	(working copy)
@@ -278,6 +278,16 @@
    */
   int fd;
 
+  /**
+   * Function to call when a connection: upgrade
+   * header is present.  May be NULL.
+   */
+  MHD_UpgradeHandler upgrade;
+
+  /**
+   * Closure argument to notify_completed.
+   */
+  void *upgrade_cls;
 };
 
 
Index: src/daemon/response.c
===================================================================
--- src/daemon/response.c	(revision 23229)
+++ src/daemon/response.c	(working copy)
@@ -408,7 +408,28 @@
 					mode == MHD_RESPMEM_MUST_COPY);
 }
 
+/**
+ * Create a response object.  The response object can be extended with
+ * header information and then be used any number of times.
+ *
+ * @param upgrade_handler function to call with the 'upgraded' socket
+ * @param upgrade_handler_cls closure for 'upgrade_handler'
+ * @return NULL on error (i.e. invalid arguments, out of memory)
+ */
+struct MHD_Response *
+MHD_create_response_for_upgrade (MHD_UpgradeHandler upgrade_handler,
+				 void *upgrade_handler_cls)
+{
+  struct MHD_Response *response;
 
+  response = MHD_create_response_from_data (0, NULL, 0, 1);
+
+  response->upgrade = upgrade_handler;
+  response->upgrade_cls = upgrade_handler_cls;
+
+  return response;
+}
+
 /**
  * Destroy a response object and associated resources.  Note that
  * libmicrohttpd may keep some of the resources around if the response
Index: src/include/microhttpd.h
===================================================================
--- src/include/microhttpd.h	(revision 23229)
+++ src/include/microhttpd.h	(working copy)
@@ -1420,13 +1420,15 @@
  *                to some other protocol.  This function must
  *                take over the communication and is ultimately
  *                responsible for closing the socket.
+ * @return MHD_NO on error, the handler want to stop the upgrade process,
+ *                the connection is still handle by MHD.
+ *         MHD_YES on success, the socket will should not be handled by MHD any more.
  */
-typedef void (*MHD_UpgradeHandler)(void *cls,
+typedef int (*MHD_UpgradeHandler) (void *cls,
 				   struct MHD_Connection *connection,
 				   void **con_cls,
 				   int upgrade_socket);
 
-#if 0
 /**
  * Create a response object that can be used for 101 UPGRADE
  * responses, for example to implement websockets.  After sending the
@@ -1459,7 +1461,6 @@
 struct MHD_Response *
 MHD_create_response_for_upgrade (MHD_UpgradeHandler upgrade_handler,
 				 void *upgrade_handler_cls);
-#endif
 
 /**
  * Destroy a response object and associated resources.  Note that
Index: src/testcurl/Makefile.am
===================================================================
--- src/testcurl/Makefile.am	(revision 23229)
+++ src/testcurl/Makefile.am	(working copy)
@@ -62,7 +62,8 @@
 endif
 
 noinst_PROGRAMS = \
-  daemon_options_test
+  daemon_options_test \
+  daemontest_upgrade
 
 if ENABLE_DAUTH
   check_PROGRAMS += \
@@ -153,6 +154,12 @@
   $(top_builddir)/src/daemon/libmicrohttpd.la \
   @LIBCURL@ 
 
+daemontest_upgrade_SOURCES = \
+  daemontest_upgrade.c
+daemontest_upgrade_LDADD = \
+  $(top_builddir)/src/daemon/libmicrohttpd.la \
+  @LIBCURL@ 
+
 daemontest_process_headers_SOURCES = \
   daemontest_process_headers.c
 daemontest_process_headers_LDADD = \
mhd_upgrade.patch (5,620 bytes)   
daemontest_upgrade.c (8,308 bytes)   
/*
     This file is part of libmicrohttpd
     (C) 2007 Christian Grothoff
     (C) 2012 Bertrand Baudet

     libmicrohttpd is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published
     by the Free Software Foundation; either version 2, or (at your
     option) any later version.

     libmicrohttpd 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
     General Public License for more details.

     You should have received a copy of the GNU General Public License
     along with libmicrohttpd; see the file COPYING.  If not, write to the
     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
     Boston, MA 02111-1307, USA.
*/

/**
 * @file daemontest_upgrade.c
 * @brief  Testcase for libmicrohttpd 'Connection: Upgrade' header field.
 * @author Bertrand Baudet
 * @author Christian Grothoff
 */

#include "MHD_config.h"
#include "platform.h"
#include <curl/curl.h>
#include <microhttpd.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#ifndef WINDOWS
#include <unistd.h>
#endif

static int oneone;

struct CBC
{
  char *buf;
  size_t pos;
  size_t size;
};

/**
 */
static size_t
copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
{
  struct CBC *cbc = ctx;

  if (cbc->pos + size * nmemb > cbc->size)
    return 0;                   /* overflow */
  memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
  cbc->pos += size * nmemb;
  return size * nmemb;
}

/**
 */
static int mhd_socket = -1;
static struct MHD_Daemon *mhd_daemon;

/**
 */
static curl_socket_t
curl_open_function(void *clientp, curlsocktype purpose, struct curl_sockaddr *address)
{
  printf("%s\n", __FUNCTION__);
  return mhd_socket;
}

/**
 */
static curl_socket_t
curl_sockopt(void *clientp, curlsocktype purpose, struct curl_sockaddr *address)
{
  printf("%s\n", __FUNCTION__);
  return CURL_SOCKOPT_ALREADY_CONNECTED;
}
/**
 */
static int
test_socket(const char *fnct, int sock)
{
  int ret;

  ret = fcntl(sock, F_GETFL);
  printf("%s: fcntl (socket[%d]) = %d\n", fnct, sock, ret);
  if (ret == -1)
    {
      printf("Failed to get socket status\n");
      return -1;
    }
  else
    {
      if (ret & O_RDONLY)
        printf("Socket is READ ONLY\n");
      else if (ret & O_WRONLY)
        printf("Socket is WRITE ONLY\n");
      else if (ret & O_RDWR)
        printf("Socket is READ WRITE\n");
    }
  return 0;
}

/**
 */
static int
upgrade_cb (void *cls, struct MHD_Connection *connection, void **con_cls, int socket)
{
  CURLcode errornum;
  CURL *c;
  int ret;

  printf("%s: cls: %p -- connection: %p -- con_cls: %p -- socket: %d\n", __FUNCTION__, cls, connection, con_cls, socket);

  mhd_socket = socket;

  test_socket(__FUNCTION__, mhd_socket);

  ret = MHD_add_connection(mhd_daemon, mhd_socket, NULL, 0);
  if (ret == MHD_NO)
    {
      printf("Failed to add connection\n");
    }

  c = curl_easy_init ();
  curl_easy_setopt (c, CURLOPT_OPENSOCKETFUNCTION, &curl_open_function);
  curl_easy_setopt (c, CURLOPT_SOCKOPTFUNCTION, &curl_sockopt);
  curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:12345/event");
  if (oneone)
    curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  else
    curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);

  errornum = curl_easy_perform (c);
  if (CURLE_OK != errornum)
    {
      fprintf (stderr,
               "%s: curl_easy_perform failed: [%d]:'%s'\n",
	       __FUNCTION__,
	       errornum,
               curl_easy_strerror (errornum));
    }
  return MHD_YES;
}

/**
 * Connection upgrade requested?
 * Check for headers:
 *    Connection: Upgrade
 *    Upgrade: <XXXX>
 *
 * @param connection connection to test
 * @param proto returns the protocol value from the 'Upgrade' header
 */
static int
connection_upgrade_requested (struct MHD_Connection *connection, char **proto)
{
  const char *h_value;

  h_value = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONNECTION);
  if (h_value == NULL) return 0;
  if (0 != strcasecmp (h_value, MHD_HTTP_HEADER_UPGRADE)) return 0;

  h_value = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_UPGRADE);
  if (h_value == NULL) return 0;

  *proto = (char*)h_value;

  return 1;
}

/**
 */
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;
  char *proto = NULL;
  int upgrade;
  int ret;

  printf("%s: %s %s\n", __FUNCTION__, method, url);

//  if (0 != strcmp (method, MHD_HTTP_METHOD_POST))
//    {
//      printf ("METHOD: %s\n", method);
//      return MHD_NO;            /* unexpected method */
//    }

  if (&aptr != *ptr)
    {
      *ptr = &aptr;
      return MHD_YES;
    }
  *ptr = NULL;                  /* reset when done */

  upgrade = connection_upgrade_requested(connection, &proto);
  if (upgrade == 1)
    {
      response = MHD_create_response_for_upgrade(upgrade_cb, NULL);
      ret = MHD_add_response_header(response, MHD_HTTP_HEADER_CONNECTION, MHD_HTTP_HEADER_UPGRADE);
      ret = MHD_add_response_header(response, MHD_HTTP_HEADER_UPGRADE, proto);
      //ret = MHD_queue_response (connection, MHD_HTTP_SWITCHING_PROTOCOLS, response);
      ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
    }
  else
    {
      response = MHD_create_response_from_buffer (0,
    					  (void *) url,
    					  MHD_RESPMEM_MUST_COPY);
      ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
    }

  MHD_destroy_response (response);

  return MHD_YES;
}

/**
 */
static int
testUpgrade ()
{
  CURL *c;
  char buf[2048];
  struct CBC cbc;
  CURLcode errornum;
  struct curl_slist *header = NULL;
  int http_code;
  int ret = 0;

  cbc.buf = buf;
  cbc.size = 2048;
  cbc.pos = 0;
//  mhd_daemon = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
  mhd_daemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
                        12345,
			NULL, NULL, &ahc_echo, NULL,
//                        MHD_OPTION_CONNECTION_UPGRADE, &upgrade_cb, NULL,
		       	MHD_OPTION_END);
  if (mhd_daemon == NULL)
    return 1;

  c = curl_easy_init ();
  header = curl_slist_append (header, "Upgrade: PTTH/1.0");
  header = curl_slist_append (header, "Connection: Upgrade");
  curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:12345/upgrade");
  curl_easy_setopt (c, CURLOPT_HTTPHEADER, header);
  curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
  curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
  curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, 0);
  curl_easy_setopt (c, CURLOPT_NOBODY, 1);
  curl_easy_setopt (c, CURLOPT_POST, 1L);
  curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
  curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
  if (oneone)
    curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  else
    curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
  curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 15L);
  // NOTE: use of CONNECTTIMEOUT without also
  //   setting NOSIGNAL results in really weird
  //   crashes on my system!
  curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
  errornum = curl_easy_perform (c);
  curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &http_code);
  printf("HTTP code: %d\n", http_code);
  if (CURLE_OK != errornum)
    {
      fprintf (stderr,
               "curl_easy_perform failed: [%d]:'%s'\n",
	       errornum,
               curl_easy_strerror (errornum));
      ret = 2;
    }

  printf("Cleanup\n");
  curl_slist_free_all (header);
  curl_easy_cleanup (c);
  MHD_stop_daemon (mhd_daemon);

  return ret;
}


int
main (int argc, char *const *argv)
{
  unsigned int errorCount = 0;

  oneone = NULL != strstr (argv[0], "11");
  if (0 != curl_global_init (CURL_GLOBAL_WIN32))
    return 2;
  errorCount += testUpgrade ();
  if (errorCount != 0)
    fprintf (stderr, "Error (code: %u)\n", errorCount);
  curl_global_cleanup ();
  return errorCount != 0;       /* 0 == pass */
}
daemontest_upgrade.c (8,308 bytes)   

Activities

Christian Grothoff

2012-08-14 22:45

manager   ~0006276

bertrand.baudet@gmail.com wrote:

Hi,

Attached is a small patch to provide 'Connection: Upgrade' capability, with an attempt to do a test with curl.
The test is not fully functional since there are some issues with the use of curl:
* if the HTTP response code is '101' curl_perform will fail. I didn't look at curl source to understand why...
* the test attempt to do an upgrade to PTTH (http://tools.ietf.org/id/draft-lentczner-rhttp-00.txt), but reverting the use of a FD in a same process,
is not really easy. You end up reading the FD you just wrote at the same end of the connection and no data are read.

Anyway, I have done another sample test and the tcpdump capture looks OK.

Regards,
Bertrand

Christian Grothoff

2012-08-14 22:48

manager   ~0006277

The patch overall seems fine and follows the idea of the API; however, it doesn't address the main failing of the API: it doesn't work with HTTPS. Ideally, we'd want a solution where the programmer won't have to worry about HTTP vs. HTTPS at this level. The current API which exposes the underlying socket cannot work this way as we cannot (safely, nicely) offer a socket that has the same nice read/write/close functions and works with HTTPS (who would be responsible for running encryption/decryption and cleaning up afterwards, especially if, say, we're running without threads)? So we need to revisit the API...

Christian Grothoff

2016-08-27 21:32

manager   ~0011067

I've now implemented, tested and documented upgrade for HTTP connections. However, HTTPS-support and more testing would be good.

Christian Grothoff

2016-09-04 18:13

manager   ~0011085

This is now implemented and tested, including HTTPS support. Waiting for community feedback before closing.

Issue History

Date Modified Username Field Change
2012-01-24 10:52 Christian Grothoff New Issue
2012-01-31 17:52 Christian Grothoff Target Version 0.9.19 => 0.9.20
2012-07-24 01:06 Christian Grothoff Target Version 0.9.20 => 0.9.22
2012-08-14 22:44 Christian Grothoff File Added: mhd_upgrade.patch
2012-08-14 22:44 Christian Grothoff File Added: daemontest_upgrade.c
2012-08-14 22:45 Christian Grothoff Note Added: 0006276
2012-08-14 22:48 Christian Grothoff Note Added: 0006277
2012-09-01 20:55 Christian Grothoff Target Version 0.9.22 => 0.9.23
2012-11-09 21:40 Christian Grothoff Target Version 0.9.23 => 0.9.24
2012-12-25 16:45 Christian Grothoff Target Version 0.9.24 => 0.9.25
2013-05-06 12:52 Christian Grothoff Category API => libmicrohttpd API
2013-08-23 14:35 Christian Grothoff Status new => confirmed
2013-08-23 14:35 Christian Grothoff Target Version 0.9.25 => 0.9.30
2016-08-27 19:13 Christian Grothoff Assigned To => Christian Grothoff
2016-08-27 19:13 Christian Grothoff Status confirmed => assigned
2016-08-27 19:13 Christian Grothoff Priority normal => high
2016-08-27 19:13 Christian Grothoff Target Version 0.9.30 => 0.9.52
2016-08-27 21:32 Christian Grothoff Note Added: 0011067
2016-09-04 18:13 Christian Grothoff Note Added: 0011085
2016-09-26 13:46 Christian Grothoff Status assigned => resolved
2016-09-26 13:46 Christian Grothoff Fixed in Version => 0.9.52
2016-09-26 13:46 Christian Grothoff Resolution open => fixed
2016-10-17 19:21 Christian Grothoff Status resolved => closed
2024-01-21 13:24 Christian Grothoff Category libmicrohttpd API => external API