add protocol plugin for post demo
authorAndy Green <andy@warmcat.com>
Mon, 9 May 2016 01:37:01 +0000 (09:37 +0800)
committerAndy Green <andy@warmcat.com>
Mon, 9 May 2016 02:05:43 +0000 (10:05 +0800)
Signed-off-by: Andy Green <andy@warmcat.com>
CMakeLists.txt
README.coding.md
README.lwsws.md
lib/context.c
lib/libwebsockets.c
lib/libwebsockets.h
lib/parsers.c
lib/server.c
lwsws/conf.c
plugins/protocol_post_demo.c [new file with mode: 0644]
test-server/test-server-v2.0.c

index 803c49c..cf1465b 100644 (file)
@@ -1216,6 +1216,8 @@ if (NOT LWS_WITHOUT_TESTAPPS)
                              "plugins/protocol_lws_mirror.c")
                create_plugin(protocol_lws_status
                              "plugins/protocol_lws_status.c")
+               create_plugin(protocol_post_demo
+                             "plugins/protocol_post_demo.c")
 if (LWS_WITH_SERVER_STATUS)
                create_plugin(protocol_lws_server_status
                              "plugins/protocol_lws_server_status.c")
index 733508d..1e3f171 100644 (file)
@@ -542,11 +542,64 @@ enum {
        LWSMPRO_CGI,
        LWSMPRO_REDIR_HTTP,
        LWSMPRO_REDIR_HTTPS,
+       LWSMPRO_CALLBACK,
 };
 ```
 
 LWSMPRO_FILE is used for mapping url namespace to a filesystem directory and
 serve it automatically.
 
+LWSMPRO_CGI associates the url namespace with the given CGI executable, which
+runs when the URL is accessed and the output provided to the client.
 
+LWSMPRO_REDIR_HTTP and LWSMPRO_REDIR_HTTPS auto-redirect clients to the given
+origin URL.
 
+LWSMPRO_CALLBACK causes the http connection to attach to the callback
+associated with the named protocol (which may be a plugin).
+
+
+Operation of LWSMPRO_CALLBACK mounts
+------------------------------------
+
+The feature provided by CALLBACK type mounts is binding a part of the URL
+namespace to a named protocol callback handler.
+
+This allows protocol plugins to handle areas of the URL namespace.  For example
+in test-server-v2.0.c, the URL area "/formtest" is associated with the plugin
+providing "protocol-post-demo" like this
+
+```
+static const struct lws_http_mount mount_post = {
+       NULL,           /* linked-list pointer to next*/
+       "/formtest",            /* mountpoint in URL namespace on this vhost */
+       "protocol-post-demo",   /* handler */
+       NULL,   /* default filename if none given */
+       NULL,
+       0,
+       0,
+       0,
+       0,
+       0,
+       LWSMPRO_CALLBACK,       /* origin points to a callback */
+       9,                      /* strlen("/formtest"), ie length of the mountpoint */
+};
+```
+
+Client access to /formtest[anything] will be passed to the callback registered
+with the named protocol, which in this case is provided by a protocol plugin.
+
+Access by all methods, eg, GET and POST are handled by the callback.
+
+protocol-post-demo deals with accepting and responding to the html form that
+is in the test server HTML.
+
+When a connection accesses a URL related to a CALLBACK type mount, the
+connection protocol is changed until the next access on the connection to a
+URL outside the same CALLBACK mount area.  User space on the connection is
+arranged to be the size of the new protocol user space allocation as given in
+the protocol struct.
+
+This allocation is only deleted / replaced when the connection accesses a
+URL region with a different protocol (or the default protocols[0] if no
+CALLBACK area matches it).
index 9d6c82a..219b6be 100644 (file)
@@ -256,7 +256,25 @@ Other mount options
        "cgi-timeout": "30"
 ```
 
-3) Cache policy of the files in the mount can also be set.  If no
+3) `callback://` protocol may be used when defining a mount to associate a
+named protocol callback with the URL namespace area.  For example
+
+```
+       {
+        "mountpoint": "/formtest",
+        "origin": "callback://protocol-post-demo"
+       }
+```
+
+All handling of client access to /formtest[anything] will be passed to the
+callback registered to the protocol "protocol-post-demo".
+
+This is useful for handling POST http body content or general non-cgi http
+payload generation inside a plugin.
+
+See the related notes in README.coding.md
+
+4) Cache policy of the files in the mount can also be set.  If no
 options are given, the content is marked uncacheable.
 
        {
index 83be24b..73e986c 100644 (file)
@@ -47,6 +47,7 @@ static const char * const mount_protocols[] = {
        "cgi://",
        ">http://",
        ">https://",
+       "callback://"
 };
 
 LWS_VISIBLE void *
index b2046ba..0d82eee 100644 (file)
@@ -2368,6 +2368,7 @@ lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len)
                "cgi://",
                ">http://",
                ">https://",
+               "callback://"
        };
        char *orig = buf, *end = buf + len - 1, first = 1;
        int n = 0;
index d0cdfb6..a130e55 100644 (file)
@@ -1600,12 +1600,13 @@ struct lws_client_connect_info {
 };
 
 enum {
-       LWSMPRO_HTTP,
-       LWSMPRO_HTTPS,
-       LWSMPRO_FILE,
-       LWSMPRO_CGI,
-       LWSMPRO_REDIR_HTTP,
-       LWSMPRO_REDIR_HTTPS,
+       LWSMPRO_HTTP            = 0,
+       LWSMPRO_HTTPS           = 1,
+       LWSMPRO_FILE            = 2,
+       LWSMPRO_CGI             = 3,
+       LWSMPRO_REDIR_HTTP      = 4,
+       LWSMPRO_REDIR_HTTPS     = 5,
+       LWSMPRO_CALLBACK        = 6,
 };
 
 LWS_EXTERN int
index 8af5079..a23f4c9 100644 (file)
@@ -82,7 +82,7 @@ lws_header_table_reset(struct lws *wsi, int autoservice)
        ah->rxlen = 0;
 
        /* since we will restart the ah, our new headers are not completed */
-       wsi->hdr_parsing_completed = 0;
+       // wsi->hdr_parsing_completed = 0;
 
        /*
         * if we inherited pending rx (from socket adoption deferred
index 1343a24..4f0f363 100644 (file)
@@ -579,9 +579,10 @@ lws_http_action(struct lws *wsi)
                     uri_ptr[hm->mountpoint_len] == '/' ||
                     hm->mountpoint_len == 1)
                    ) {
-                       if ((hm->origin_protocol == LWSMPRO_CGI ||
+                       if (hm->origin_protocol == LWSMPRO_CALLBACK ||
+                           ((hm->origin_protocol == LWSMPRO_CGI ||
                             lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI)) &&
-                           hm->mountpoint_len > best) {
+                           hm->mountpoint_len > best)) {
                                best = hm->mountpoint_len;
                                hit = hm;
                        }
@@ -609,9 +610,13 @@ lws_http_action(struct lws *wsi)
                 * / at the end, we must redirect to add it so the browser
                 * understands he is one "directory level" down.
                 */
-               if ((hit->mountpoint_len > 1 || (hit->origin_protocol & 4)) &&
-                   (*s != '/' || (hit->origin_protocol & 4)) &&
-                   (hit->origin_protocol != LWSMPRO_CGI)) {
+               if ((hit->mountpoint_len > 1 ||
+                    (hit->origin_protocol == LWSMPRO_REDIR_HTTP ||
+                     hit->origin_protocol == LWSMPRO_REDIR_HTTPS)) &&
+                   (*s != '/' ||
+                    (hit->origin_protocol == LWSMPRO_REDIR_HTTP ||
+                     hit->origin_protocol == LWSMPRO_REDIR_HTTPS)) &&
+                   (hit->origin_protocol != LWSMPRO_CGI && hit->origin_protocol != LWSMPRO_CALLBACK)) {
                        unsigned char *start = pt->serv_buf + LWS_PRE,
                                              *p = start, *end = p + 512;
                        static const char *oprot[] = {
@@ -642,6 +647,50 @@ lws_http_action(struct lws *wsi)
                        return lws_http_transaction_completed(wsi);
                }
 
+               /*
+                * A particular protocol callback is mounted here?
+                *
+                * For the duration of this http transaction, bind us to the
+                * associated protocol
+                */
+               if (hit->origin_protocol == LWSMPRO_CALLBACK) {
+
+                       for (n = 0; n < wsi->vhost->count_protocols; n++)
+                               if (!strcmp(wsi->vhost->protocols[n].name,
+                                          hit->origin)) {
+
+                                       if (wsi->protocol != &wsi->vhost->protocols[n])
+                                               if (!wsi->user_space_externally_allocated)
+                                                       lws_free_set_NULL(wsi->user_space);
+                                       wsi->protocol = &wsi->vhost->protocols[n];
+                                       if (lws_ensure_user_space(wsi)) {
+                                               lwsl_err("Unable to allocate user space\n");
+
+                                               return 1;
+                                       }
+                                       break;
+                               }
+
+                       if (n == wsi->vhost->count_protocols) {
+                               n = -1;
+                               lwsl_err("Unable to find plugin '%s'\n",
+                                        hit->origin);
+                       }
+
+                       n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP,
+                                                   wsi->user_space, uri_ptr, uri_len);
+
+                       goto after;
+               }
+
+               /* deferred cleanup and reset to protocols[0] */
+
+               if (wsi->protocol != &wsi->vhost->protocols[0])
+                       if (!wsi->user_space_externally_allocated)
+                               lws_free_set_NULL(wsi->user_space);
+
+               wsi->protocol = &wsi->vhost->protocols[0];
+
 #ifdef LWS_WITH_CGI
                /* did we hit something with a cgi:// origin? */
                if (hit->origin_protocol == LWSMPRO_CGI) {
@@ -699,10 +748,18 @@ lws_http_action(struct lws *wsi)
                        n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP,
                                            wsi->user_space, uri_ptr, uri_len);
                }
-       } else
+       } else {
+               /* deferred cleanup and reset to protocols[0] */
+
+               if (wsi->protocol != &wsi->vhost->protocols[0])
+                       if (!wsi->user_space_externally_allocated)
+                               lws_free_set_NULL(wsi->user_space);
+               wsi->protocol = &wsi->vhost->protocols[0];
+
                n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP,
                                    wsi->user_space, uri_ptr, uri_len);
-
+       }
+after:
        if (n) {
                lwsl_info("LWS_CALLBACK_HTTP closing\n");
 
@@ -1185,6 +1242,7 @@ lws_http_transaction_completed(struct lws *wsi)
        /* otherwise set ourselves up ready to go again */
        wsi->state = LWSS_HTTP;
        wsi->mode = LWSCM_HTTP_SERVING;
+       /* reset of non [0] protocols (and freeing of user_space) is deferred */
        wsi->u.http.content_length = 0;
        wsi->hdr_parsing_completed = 0;
 #ifdef LWS_WITH_ACCESS_LOG
index f222ed6..f09d9ad 100644 (file)
@@ -277,6 +277,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
                        "cgi://",
                        ">http://",
                        ">https://",
+                       "callback://"
                };
 
                if (!a->m.mountpoint || !a->m.origin) {
diff --git a/plugins/protocol_post_demo.c b/plugins/protocol_post_demo.c
new file mode 100644 (file)
index 0000000..af356f2
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ * ws protocol handler plugin for "dumb increment"
+ *
+ * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * The person who associated a work with this deed has dedicated
+ * the work to the public domain by waiving all of his or her rights
+ * to the work worldwide under copyright law, including all related
+ * and neighboring rights, to the extent allowed by law. You can copy,
+ * modify, distribute and perform the work, even for commercial purposes,
+ * all without asking permission.
+ *
+ * These test plugins are intended to be adapted for use in your code, which
+ * may be proprietary.  So unlike the library itself, they are licensed
+ * Public Domain.
+ */
+#include "../lib/libwebsockets.h"
+#include <string.h>
+
+struct per_session_data__post_demo {
+       char post_string[256];
+       char result[500 + LWS_PRE];
+       int result_len;
+};
+
+static int
+callback_post_demo(struct lws *wsi, enum lws_callback_reasons reason,
+                  void *user, void *in, size_t len)
+{
+       struct per_session_data__post_demo *pss =
+                       (struct per_session_data__post_demo *)user;
+       unsigned char buffer[LWS_PRE + 512];
+       unsigned char *p, *start, *end;
+       int n;
+
+       switch (reason) {
+       case LWS_CALLBACK_HTTP:
+               lwsl_debug("LWS_CALLBACK_HTTP\n");
+               if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI))
+                       return 0;
+               break;
+
+       case LWS_CALLBACK_HTTP_BODY:
+               lwsl_debug("LWS_CALLBACK_HTTP_BODY: len %d\n", (int)len);
+               strncpy(pss->post_string, in, sizeof (pss->post_string) -1);
+               pss->post_string[sizeof(pss->post_string) - 1] = '\0';
+
+               if (len < sizeof(pss->post_string) - 1)
+                       pss->post_string[len] = '\0';
+               break;
+
+       case LWS_CALLBACK_HTTP_WRITEABLE:
+               lwsl_debug("LWS_CALLBACK_HTTP_WRITEABLE: sending %d\n", pss->result_len);
+               n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE,
+                             pss->result_len, LWS_WRITE_HTTP);
+               if (n < 0)
+                       return 1;
+               goto try_to_reuse;
+
+       case LWS_CALLBACK_HTTP_BODY_COMPLETION:
+               lwsl_debug("LWS_CALLBACK_HTTP_BODY_COMPLETION\n");
+               /*
+                * the whole of the sent body arrived,
+                * respond to the client with a redirect to show the
+                * results
+                */
+               pss->result_len = sprintf((char *)pss->result + LWS_PRE,
+                           "<html><body><h1>Form results</h1>'%s'<br>"
+                           "</body></html>", pss->post_string);
+
+               p = buffer + LWS_PRE;
+               start = p;
+               end = p + sizeof(buffer) - LWS_PRE;
+
+               if (lws_add_http_header_status(wsi, 200, &p, end))
+                       return 1;
+
+               if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
+                               (unsigned char *)"text/html", 9, &p, end))
+                       return 1;
+               if (lws_add_http_header_content_length(wsi, pss->result_len, &p, end))
+                       return 1;
+               if (lws_finalize_http_header(wsi, &p, end))
+                       return 1;
+
+               n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
+               if (n < 0)
+                       return 1;
+
+               /*
+                *  send the payload next time, in case would block after
+                * headers
+                */
+               lws_callback_on_writable(wsi);
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+
+try_to_reuse:
+       if (lws_http_transaction_completed(wsi))
+               return -1;
+
+       return 0;
+}
+
+static const struct lws_protocols protocols[] = {
+       {
+               "protocol-post-demo",
+               callback_post_demo,
+               sizeof(struct per_session_data__post_demo),
+               1024,
+       },
+};
+
+LWS_VISIBLE int
+init_protocol_post_demo(struct lws_context *context,
+                       struct lws_plugin_capability *c)
+{
+       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+                        c->api_magic);
+               return 1;
+       }
+
+       c->protocols = protocols;
+       c->count_protocols = ARRAY_SIZE(protocols);
+       c->extensions = NULL;
+       c->count_extensions = 0;
+
+       return 0;
+}
+
+LWS_VISIBLE int
+destroy_protocol_post_demo(struct lws_context *context)
+{
+       return 0;
+}
index debe72b..ae17078 100644 (file)
@@ -70,13 +70,32 @@ static const struct lws_extension exts[] = {
 };
 
 /*
+ * mount a handler for a section of the URL space
+ */
+
+static const struct lws_http_mount mount_post = {
+       NULL,           /* linked-list pointer to next*/
+       "/formtest",            /* mountpoint in URL namespace on this vhost */
+       "protocol-post-demo",   /* handler */
+       NULL,   /* default filename if none given */
+       NULL,
+       0,
+       0,
+       0,
+       0,
+       0,
+       LWSMPRO_CALLBACK,       /* origin points to a callback */
+       9,                      /* strlen("/formtest"), ie length of the mountpoint */
+};
+
+/*
  * mount a filesystem directory into the URL space at /
  * point it to our /usr/share directory with our assets in
  * stuff from here is autoserved by the library
  */
 
 static const struct lws_http_mount mount = {
-       NULL,           /* linked-list pointer to next, but we only have one */
+       (struct lws_http_mount *)&mount_post,           /* linked-list pointer to next*/
        "/",            /* mountpoint in URL namespace on this vhost */
        LOCAL_RESOURCE_PATH, /* where to go on the filesystem for that */
        "test.html",    /* default filename if none given */
@@ -108,9 +127,16 @@ static const struct lws_protocol_vhost_options pvo_opt = {
  * linked-list.  We can also give the plugin per-vhost options here.
  */
 
-static const struct lws_protocol_vhost_options pvo_2 = {
+static const struct lws_protocol_vhost_options pvo_3 = {
        NULL,
        NULL,
+       "protocol-post-demo",
+       "" /* ignored, just matches the protocol name above */
+};
+
+static const struct lws_protocol_vhost_options pvo_2 = {
+       &pvo_3,
+       NULL,
        "lws-status",
        "" /* ignored, just matches the protocol name above */
 };
@@ -160,7 +186,6 @@ static const struct option options[] = {
        { "ssl-crl",  required_argument,                NULL, 'R' },
 #endif
 #endif
-       { "libev",  no_argument,                NULL, 'e' },
 #ifndef LWS_NO_DAEMONIZE
        { "daemonize",  no_argument,            NULL, 'D' },
 #endif
@@ -200,13 +225,10 @@ int main(int argc, char **argv)
        info.port = 7681;
 
        while (n >= 0) {
-               n = getopt_long(argc, argv, "ei:hsap:d:Dr:C:K:A:R:vu:g:", options, NULL);
+               n = getopt_long(argc, argv, "i:hsap:d:Dr:C:K:A:R:vu:g:", options, NULL);
                if (n < 0)
                        continue;
                switch (n) {
-               case 'e':
-                       opts |= LWS_SERVER_OPTION_LIBEV;
-                       break;
 #ifndef LWS_NO_DAEMONIZE
                case 'D':
                        daemonize = 1;