View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0002102 | libmicrohttpd | external API | public | 2012-01-24 10:52 | 2016-10-17 19:21 |
| Reporter | Christian Grothoff | Assigned To | Christian Grothoff | ||
| Priority | high | Severity | feature | Reproducibility | N/A |
| Status | closed | Resolution | fixed | ||
| Product Version | 0.9.18 | ||||
| Target Version | 0.9.52 | Fixed in Version | 0.9.52 | ||
| Summary | 0002102: support for WebSockets is desired | ||||
| Description | Preliminary API was proposed on the mailinglist (under review). The implementation can then be tested using http://www.tavendo.de/autobahn/home.html | ||||
| Tags | No 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 = \
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, ©Buffer);
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 */
}
| ||||
|
|
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 |
|
|
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... |
|
|
I've now implemented, tested and documented upgrade for HTTP connections. However, HTTPS-support and more testing would be good. |
|
|
This is now implemented and tested, including HTTPS support. Waiting for community feedback before closing. |
| 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 |