proxy rewrite
authorAndy Green <andy@warmcat.com>
Sun, 20 Mar 2016 03:59:53 +0000 (11:59 +0800)
committerAndy Green <andy@warmcat.com>
Sun, 20 Mar 2016 03:59:53 +0000 (11:59 +0800)
If you enable -DLWS_WITH_HTTP_PROXY=1 at cmake, the test server has a
new URI path http://localhost:7681/proxytest If you visit here, a client
connection to http://example.com:80 is spawned, and the results piped on
to your original connection.

Also with LWS_WITH_HTTP_PROXY enabled at cmake, lws wants to link to an
additional library, "libhubbub".  This allows lws to do html rewriting on the
fly, adjusting proxied urls in a lightweight and fast way.

13 files changed:
CMakeLists.txt
changelog
lib/client-handshake.c
lib/client.c
lib/libwebsockets.c
lib/libwebsockets.h
lib/output.c
lib/private-libwebsockets.h
lib/rewrite.c [new file with mode: 0644]
lib/server.c
lib/service.c
lws_config.h.in
test-server/test-server-http.c

index bd6bb36..490abd6 100644 (file)
@@ -88,6 +88,8 @@ option(LWS_WITH_HTTP2 "Compile with support for http2" OFF)
 option(LWS_MBED3 "Platform is MBED3" OFF)
 option(LWS_SSL_SERVER_WITH_ECDH_CERT "Include SSL server use ECDH certificate" OFF)
 option(LWS_WITH_CGI "Include CGI (spawn process with network-connected stdin/out/err) APIs" OFF)
+option(LWS_WITH_HTTP_PROXY "Support for rewriting HTTP proxying" OFF)
+
 
 if (DEFINED YOTTA_WEBSOCKETS_VERSION_STRING)
 
@@ -108,6 +110,9 @@ if (WIN32)
 set(LWS_MAX_SMP 1)
 endif()
 
+if (LWS_WITH_HTTP_PROXY AND (LWS_WITHOUT_CLIENT OR LWS_WITHOUT_SERVER))
+       message(FATAL_ERROR "You have to enable both client and server for http proxy")
+endif()
 
 # Allow the user to override installation directories.
 set(LWS_INSTALL_LIB_DIR       lib CACHE PATH "Installation directory for libraries")
@@ -211,6 +216,7 @@ if (LWS_WITH_LIBUV)
        endif()
 endif()
 
+
 # FIXME: This must be runtime-only option.
 # The base dir where the test-apps look for the SSL certs.
 set(LWS_OPENSSL_CLIENT_CERTS ../share CACHE PATH "Server SSL certificate directory")
@@ -489,6 +495,11 @@ if (NOT LWS_WITHOUT_EXTENSIONS)
                lib/extension-permessage-deflate.c)
 endif()
 
+if (LWS_WITH_HTTP_PROXY)
+       list(APPEND SOURCES
+               lib/rewrite.c)
+endif()
+
 if (LWS_WITH_LIBEV)
        list(APPEND SOURCES
                lib/libev.c)
@@ -734,6 +745,11 @@ if (LWS_WITH_LIBUV)
        include_directories("${LIBUV_INCLUDE_DIRS}")
        list(APPEND LIB_LIST ${LIBUV_LIBRARIES})
 endif()
+if (LWS_WITH_HTTP_PROXY)
+       find_library(LIBHUBBUB_LIBRARIES NAMES libhubbub)
+       list(APPEND LIB_LIST ${LIBHUBBUB_LIBRARIES} )
+endif()
+
 
 #
 # Platform specific libs.
@@ -1227,6 +1243,8 @@ message(" LWS_SSL_SERVER_WITH_ECDH_CERT = ${LWS_SSL_SERVER_WITH_ECDH_CERT}")
 message(" LWS_MAX_SMP = ${LWS_MAX_SMP}")
 message(" LWS_WITH_CGI = ${LWS_WITH_CGI}")
 message(" LWS_HAVE_OPENSSL_ECDH_H = ${LWS_HAVE_OPENSSL_ECDH_H}")
+message(" LWS_WITH_HTTP_PROXY = ${LWS_WITH_HTTP_PROXY}")
+message(" LIBHUBBUB_LIBRARIES = ${LIBHUBBUB_LIBRARIES}")
 message("---------------------------------------------------------------------")
 
 # These will be available to parent projects including libwebsockets using add_subdirectory()
index ef5a406..bc580d6 100644 (file)
--- a/changelog
+++ b/changelog
@@ -81,9 +81,14 @@ protocol string to its argument in URL format.  If so, it stays in http[s]
 client mode and doesn't upgrade to ws[s], allowing you to do generic http client
 operations.  Receiving transfer-encoding: chunked is supported.
 
-9) The test server has a new URI path http://localhost:7681/proxytest
-If you visit here, a client connection to http://example.com:80 is spawned,
-and the results piped on to your original connection.
+9) If you enable -DLWS_WITH_HTTP_PROXY=1 at cmake, the test server has a
+new URI path http://localhost:7681/proxytest If you visit here, a client
+connection to http://example.com:80 is spawned, and the results piped on
+to your original connection.
+
+10) Also with LWS_WITH_HTTP_PROXY enabled at cmake, lws wants to link to an
+additional library, "libhubbub".  This allows lws to do html rewriting on the
+fly, adjusting proxied urls in a lightweight and fast way.
 
 
 User API additions
index 5504abe..b3419a1 100644 (file)
@@ -338,6 +338,115 @@ lws_client_reset(struct lws *wsi, int ssl, const char *address, int port, const
        return lws_client_connect_2(wsi);
 }
 
+
+static hubbub_error
+html_parser_cb(const hubbub_token *token, void *pw)
+{
+       struct lws_rewrite *r = (struct lws_rewrite *)pw;
+       char buf[1024], *start = buf + LWS_PRE, *p = start,
+            *end = &buf[sizeof(buf) - 1];
+       size_t i;
+
+       switch (token->type) {
+       case HUBBUB_TOKEN_DOCTYPE:
+
+               p += snprintf(p, end - p, "<!DOCTYPE %.*s %s ",
+                               (int) token->data.doctype.name.len,
+                               token->data.doctype.name.ptr,
+                               token->data.doctype.force_quirks ?
+                                               "(force-quirks) " : "");
+
+               if (token->data.doctype.public_missing)
+                       printf("\tpublic: missing\n");
+               else
+                       p += snprintf(p, end - p, "PUBLIC \"%.*s\"\n",
+                               (int) token->data.doctype.public_id.len,
+                               token->data.doctype.public_id.ptr);
+
+               if (token->data.doctype.system_missing)
+                       printf("\tsystem: missing\n");
+               else
+                       p += snprintf(p, end - p, " \"%.*s\">\n",
+                               (int) token->data.doctype.system_id.len,
+                               token->data.doctype.system_id.ptr);
+
+               break;
+       case HUBBUB_TOKEN_START_TAG:
+               p += snprintf(p, end - p, "<%.*s", (int)token->data.tag.name.len,
+                               token->data.tag.name.ptr);
+
+/*                             (token->data.tag.self_closing) ?
+                                               "(self-closing) " : "",
+                               (token->data.tag.n_attributes > 0) ?
+                                               "attributes:" : "");
+*/
+               for (i = 0; i < token->data.tag.n_attributes; i++) {
+                       if (!hstrcmp(&token->data.tag.attributes[i].name, "href", 4) ||
+                           !hstrcmp(&token->data.tag.attributes[i].name, "action", 6) ||
+                           !hstrcmp(&token->data.tag.attributes[i].name, "src", 3)) {
+                               const char *pp = (const char *)token->data.tag.attributes[i].value.ptr;
+                               int plen = (int) token->data.tag.attributes[i].value.len;
+
+                               if (!hstrcmp(&token->data.tag.attributes[i].value,
+                                            r->from, r->from_len)) {
+                                       pp += r->from_len;
+                                       plen -= r->from_len;
+                               }
+                               p += snprintf(p, end - p, " %.*s=\"%s/%.*s\"",
+                                      (int) token->data.tag.attributes[i].name.len,
+                                      token->data.tag.attributes[i].name.ptr,
+                                      r->to, plen, pp);
+
+                       } else
+
+                               p += snprintf(p, end - p, " %.*s=\"%.*s\"",
+                                       (int) token->data.tag.attributes[i].name.len,
+                                       token->data.tag.attributes[i].name.ptr,
+                                       (int) token->data.tag.attributes[i].value.len,
+                                       token->data.tag.attributes[i].value.ptr);
+               }
+               p += snprintf(p, end - p, ">\n");
+               break;
+       case HUBBUB_TOKEN_END_TAG:
+               p += snprintf(p, end - p, "</%.*s", (int) token->data.tag.name.len,
+                               token->data.tag.name.ptr);
+/*
+                               (token->data.tag.self_closing) ?
+                                               "(self-closing) " : "",
+                               (token->data.tag.n_attributes > 0) ?
+                                               "attributes:" : "");
+*/
+               for (i = 0; i < token->data.tag.n_attributes; i++) {
+                       p += snprintf(p, end - p, " %.*s='%.*s'\n",
+                               (int) token->data.tag.attributes[i].name.len,
+                               token->data.tag.attributes[i].name.ptr,
+                               (int) token->data.tag.attributes[i].value.len,
+                               token->data.tag.attributes[i].value.ptr);
+               }
+               p += snprintf(p, end - p, ">\n");
+               break;
+       case HUBBUB_TOKEN_COMMENT:
+               p += snprintf(p, end - p, "<!-- %.*s -->\n",
+                               (int) token->data.comment.len,
+                               token->data.comment.ptr);
+               break;
+       case HUBBUB_TOKEN_CHARACTER:
+               p += snprintf(p, end - p, "%.*s", (int) token->data.character.len,
+                               token->data.character.ptr);
+               break;
+       case HUBBUB_TOKEN_EOF:
+               p += snprintf(p, end - p, "\n");
+               break;
+       }
+
+       if (user_callback_handle_rxflow(r->wsi->protocol->callback,
+                       r->wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ,
+                       r->wsi->user_space, start, p - start))
+               return -1;
+
+       return HUBBUB_OK;
+}
+
 /**
  * lws_client_connect_via_info() - Connect to another websocket server
  * @i:pointer to lws_client_connect_info struct
@@ -452,6 +561,11 @@ lws_client_connect_via_info(struct lws_client_connect_info *i)
                i->parent_wsi->child_list = wsi;
        }
 
+       if (i->uri_replace_to)
+               wsi->rw = lws_rewrite_create(wsi, html_parser_cb,
+                                            i->uri_replace_from,
+                                            i->uri_replace_to);
+
        return wsi;
 
 bail:
index 37779be..77eb7f0 100644 (file)
@@ -622,6 +622,15 @@ lws_client_interpret_server_handshake(struct lws *wsi)
                        goto bail2;
                }
 
+#ifndef LWS_NO_CLIENT
+       wsi->perform_rewrite = 0;
+       if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)) {
+               if (!strncmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE),
+                               "text/html", 9))
+                       wsi->perform_rewrite = 1;
+       }
+#endif
+
                /* allocate the per-connection user memory (if any) */
                if (lws_ensure_user_space(wsi)) {
                        lwsl_err("Problem allocating wsi user mem\n");
index 04c2ef5..7942ac1 100644 (file)
@@ -158,12 +158,18 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason)
        if (wsi->child_list) {
                wsi2 = wsi->child_list;
                while (wsi2) {
-                       lwsl_notice("%s: closing %p: close child %p\n",
-                                       __func__, wsi, wsi2);
+                       //lwsl_notice("%s: closing %p: close child %p\n",
+                       //              __func__, wsi, wsi2);
                        wsi1 = wsi2->sibling_list;
+                       //lwsl_notice("%s: closing %p: next sibling %p\n",
+                       //              __func__, wsi2, wsi1);
+                       wsi2->parent = NULL;
+                       /* stop it doing shutdown processing */
+                       wsi2->socket_is_permanently_unusable = 1;
                        lws_close_free_wsi(wsi2, reason);
                        wsi2 = wsi1;
                }
+               wsi->child_list = NULL;
        }
 
 #ifdef LWS_WITH_CGI
@@ -186,7 +192,6 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason)
 
        if (wsi->mode == LWSCM_HTTP_SERVING_ACCEPTED &&
            wsi->u.http.fd != LWS_INVALID_FILE) {
-               lwsl_debug("closing http file\n");
                lws_plat_file_close(wsi, wsi->u.http.fd);
                wsi->u.http.fd = LWS_INVALID_FILE;
                context->protocols[0].callback(wsi, LWS_CALLBACK_CLOSED_HTTP,
@@ -363,7 +368,7 @@ just_kill_connection:
        if (wsi->state != LWSS_SHUTDOWN &&
            reason != LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY &&
            !wsi->socket_is_permanently_unusable) {
-               lwsl_info("%s: shutting down connection: %p\n", __func__, wsi);
+               lwsl_info("%s: shutting down connection: %p (sock %d)\n", __func__, wsi, wsi->sock);
                n = shutdown(wsi->sock, SHUT_WR);
                if (n)
                        lwsl_debug("closing: shutdown ret %d\n", LWS_ERRNO);
@@ -384,7 +389,13 @@ just_kill_connection:
        }
 #endif
 
-       lwsl_info("%s: real just_kill_connection: %p\n", __func__, wsi);
+       lwsl_info("%s: real just_kill_connection: %p (sockfd %d)\n", __func__,
+                 wsi, wsi->sock);
+
+       if (wsi->rw) {
+               lws_rewrite_destroy(wsi->rw);
+               wsi->rw = NULL;
+       }
 
        /*
         * we won't be servicing or receiving anything further from this guy
@@ -502,6 +513,7 @@ lws_close_free_wsi_final(struct lws *wsi)
 
        if (!lws_ssl_close(wsi) && lws_socket_is_valid(wsi->sock)) {
 #if LWS_POSIX
+               //lwsl_err("*** closing sockfd %d\n", wsi->sock);
                n = compatible_close(wsi->sock);
                if (n)
                        lwsl_debug("closing: close ret %d\n", LWS_ERRNO);
@@ -1742,12 +1754,11 @@ lws_cgi_write_split_stdout_headers(struct lws *wsi)
 
                /* ran out of input, ended the headers, or filled up the headers buf */
                if (!n || wsi->hdr_state == LHCS_PAYLOAD || (p + 4) == end) {
-lwsl_err("a\n");
+
                        m = lws_write(wsi, (unsigned char *)start,
                                      p - start, LWS_WRITE_HTTP_HEADERS);
                        if (m < 0)
                                return -1;
-lwsl_err("b\n");
                        /* writeability becomes uncertain now we wrote
                         * something, we must return to the event loop
                         */
@@ -1755,7 +1766,7 @@ lwsl_err("b\n");
                        return 0;
                }
        }
-       lwsl_err("%s: stdout\n", __func__);
+       //lwsl_err("%s: stdout\n", __func__);
        n = read(lws_get_socket_fd(wsi->cgi->stdwsi[LWS_STDOUT]),
                 start, sizeof(buf) - LWS_PRE);
 
index f4b20c6..72b8865 100644 (file)
@@ -364,6 +364,7 @@ enum lws_callback_reasons {
        LWS_CALLBACK_CLOSED_CLIENT_HTTP                         = 45,
        LWS_CALLBACK_RECEIVE_CLIENT_HTTP                        = 46,
        LWS_CALLBACK_COMPLETED_CLIENT_HTTP                      = 47,
+       LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ                   = 48,
 
        /****** add new things just above ---^ ******/
 
@@ -1415,6 +1416,9 @@ struct lws_context_creation_info {
  * @parent_wsi:        if another wsi is responsible for this connection, give it here.
  *             this is used to make sure if the parent closes so do any
  *             child connections first.
+ * @uri_replace_from: if non-NULL, when this string is found in URIs in
+ *             text/html content-encoding, it's replaced with @uri_replace_to
+ * @uri_replace_to: see above
  */
 
 struct lws_client_connect_info {
@@ -1431,6 +1435,8 @@ struct lws_client_connect_info {
        const struct lws_extension *client_exts;
        const char *method;
        struct lws *parent_wsi;
+       const char *uri_replace_from;
+       const char *uri_replace_to;
 
        /* Add new things just above here ---^
         * This is part of the ABI, don't needlessly break compatibility
@@ -1893,6 +1899,9 @@ LWS_VISIBLE LWS_EXTERN int
 lws_cgi_kill(struct lws *wsi);
 #endif
 
+LWS_VISIBLE LWS_EXTERN int
+lws_http_client_read(struct lws *wsi, char **buf, int *len);
+
 /*
  * Wsi-associated File Operations access helpers
  *
index 3204490..b527e9c 100644 (file)
@@ -600,7 +600,6 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi)
 all_sent:
                if (!wsi->trunc_len && wsi->u.http.filepos == wsi->u.http.filelen) {
                        wsi->state = LWSS_HTTP;
-
                        /* we might be in keepalive, so close it off here */
                        lws_plat_file_close(wsi, wsi->u.http.fd);
                        wsi->u.http.fd = LWS_INVALID_FILE;
index 4d69bd5..b70a31b 100644 (file)
 #include <netdb.h>
 #include <signal.h>
 #include <sys/socket.h>
+#ifdef LWS_WITH_HTTP_PROXY
+#include <hubbub/hubbub.h>
+#include <hubbub/parser.h>
+#endif
 #ifdef LWS_BUILTIN_GETIFADDRS
  #include <getifaddrs.h>
 #else
 #define LWS_POLLHUP (POLLHUP|POLLERR)
 #define LWS_POLLIN (POLLIN)
 #define LWS_POLLOUT (POLLOUT)
-#define compatible_close(fd) close(fd)
+static inline int compatible_close(int fd) { return close(fd); }
 #define lws_set_blocking_send(wsi)
 
 #ifdef MBED_OPERATORS
@@ -1068,6 +1072,8 @@ enum lws_chunk_parser {
 };
 #endif
 
+struct lws_rewrite;
+
 struct lws {
 
        /* structs */
@@ -1118,6 +1124,9 @@ struct lws {
        BIO *client_bio;
        struct lws *pending_read_list_prev, *pending_read_list_next;
 #endif
+#ifndef LWS_NO_CLIENT
+       struct lws_rewrite *rw;
+#endif
 #ifdef LWS_LATENCY
        unsigned long action_start;
        unsigned long latency_start;
@@ -1144,6 +1153,8 @@ struct lws {
 #ifndef LWS_NO_CLIENT
        unsigned int do_ws:1; /* whether we are doing http or ws flow */
        unsigned int chunked:1; /* if the clientside connection is chunked */
+       unsigned int client_rx_avail:1;
+       unsigned int perform_rewrite:1;
 #endif
 #ifndef LWS_NO_EXTENSIONS
        unsigned int extension_data_pending:1;
@@ -1502,10 +1513,35 @@ lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len);
 LWS_EXTERN int LWS_WARN_UNUSED_RESULT
 lws_ssl_pending_no_ssl(struct lws *wsi);
 
-#ifndef LWS_NO_CLIENT
+#ifdef LWS_WITH_HTTP_PROXY
+struct lws_rewrite {
+       hubbub_parser *parser;
+       hubbub_parser_optparams params;
+       const char *from, *to;
+       int from_len, to_len;
+       unsigned char *p, *end;
+       struct lws *wsi;
+};
+static LWS_INLINE int hstrcmp(hubbub_string *s, const char *p, int len)
+{
+       if (s->len != len)
+               return 1;
+
+       return strncmp((const char *)s->ptr, p, len);
+}
+typedef hubbub_error (*hubbub_callback_t)(const hubbub_token *token, void *pw);
 LWS_EXTERN int lws_client_socket_service(struct lws_context *context,
                                         struct lws *wsi,
                                         struct lws_pollfd *pollfd);
+LWS_EXTERN struct lws_rewrite *
+lws_rewrite_create(struct lws *wsi, hubbub_callback_t cb, const char *from, const char *to);
+LWS_EXTERN void
+lws_rewrite_destroy(struct lws_rewrite *r);
+LWS_EXTERN int
+lws_rewrite_parse(struct lws_rewrite *r, const unsigned char *in, int in_len);
+#endif
+
+#ifndef LWS_NO_CLIENT
 #ifdef LWS_OPENSSL_SUPPORT
 LWS_EXTERN int
 lws_context_init_client_ssl(struct lws_context_creation_info *info,
diff --git a/lib/rewrite.c b/lib/rewrite.c
new file mode 100644 (file)
index 0000000..db98e55
--- /dev/null
@@ -0,0 +1,47 @@
+#include "private-libwebsockets.h"
+
+
+LWS_EXTERN struct lws_rewrite *
+lws_rewrite_create(struct lws *wsi, hubbub_callback_t cb, const char *from, const char *to)
+{
+       struct lws_rewrite *r = lws_malloc(sizeof(*r));
+
+       if (hubbub_parser_create("UTF-8", false, &r->parser) != HUBBUB_OK) {
+               lws_free(r);
+
+               return NULL;
+       }
+       r->from = from;
+       r->from_len = strlen(from);
+       r->to = to;
+       r->to_len = strlen(to);
+       r->params.token_handler.handler = cb;
+       r->wsi = wsi;
+       r->params.token_handler.pw = (void *)r;
+       if (hubbub_parser_setopt(r->parser, HUBBUB_PARSER_TOKEN_HANDLER,
+                                &r->params) != HUBBUB_OK) {
+               lws_free(r);
+
+               return NULL;
+       }
+
+       return r;
+}
+
+LWS_EXTERN int
+lws_rewrite_parse(struct lws_rewrite *r,
+                 const unsigned char *in, int in_len)
+{
+       if (hubbub_parser_parse_chunk(r->parser, in, in_len) != HUBBUB_OK)
+               return -1;
+
+       return 0;
+}
+
+LWS_EXTERN void
+lws_rewrite_destroy(struct lws_rewrite *r)
+{
+       hubbub_parser_destroy(r->parser);
+       lws_free(r);
+}
+
index 07e20af..5a19a16 100644 (file)
@@ -779,7 +779,7 @@ lws_adopt_socket(struct lws_context *context, lws_sockfd_type accept_fd)
                return NULL;
        }
 
-       lwsl_debug("%s: new wsi %p\n", __func__, new_wsi);
+       lwsl_info("%s: new wsi %p, sockfd %d\n", __func__, new_wsi, accept_fd);
 
        new_wsi->sock = accept_fd;
 
index 9acdcb1..368043d 100644 (file)
@@ -473,6 +473,134 @@ lws_service_flag_pending(struct lws_context *context, int tsi)
        return forced;
 }
 
+
+/*
+ *
+ */
+LWS_VISIBLE int
+lws_http_client_read(struct lws *wsi, char **buf, int *len)
+{
+       int rlen, n;
+
+       rlen = lws_ssl_capable_read(wsi, (unsigned char *)*buf, *len);
+       if (rlen < 0)
+               return -1;
+
+       *len = rlen;
+       if (rlen == 0)
+               return 0;
+
+//     lwsl_err("%s: read %d\n", __func__, rlen);
+
+       /* allow the source to signal he has data again next time */
+       wsi->client_rx_avail = 0;
+       lws_change_pollfd(wsi, 0, LWS_POLLIN);
+
+       /*
+        * server may insist on transfer-encoding: chunked,
+        * so http client must deal with it
+        */
+spin_chunks:
+       while (wsi->chunked && (wsi->chunk_parser != ELCP_CONTENT) && *len) {
+               switch (wsi->chunk_parser) {
+               case ELCP_HEX:
+                       if ((*buf)[0] == '\x0d') {
+                               wsi->chunk_parser = ELCP_CR;
+                               break;
+                       }
+                       n = char_to_hex((*buf)[0]);
+                       if (n < 0)
+                               return -1;
+                       wsi->chunk_remaining <<= 4;
+                       wsi->chunk_remaining |= n;
+                       break;
+               case ELCP_CR:
+                       if ((*buf)[0] != '\x0a')
+                               return -1;
+                       wsi->chunk_parser = ELCP_CONTENT;
+                       lwsl_info("chunk %d\n", wsi->chunk_remaining);
+                       if (wsi->chunk_remaining)
+                               break;
+                       lwsl_info("final chunk\n");
+                       goto completed;
+
+               case ELCP_CONTENT:
+                       break;
+
+               case ELCP_POST_CR:
+                       if ((*buf)[0] != '\x0d')
+                               return -1;
+
+                       wsi->chunk_parser = ELCP_POST_LF;
+                       break;
+
+               case ELCP_POST_LF:
+                       if ((*buf)[0] != '\x0a')
+                               return -1;
+
+                       wsi->chunk_parser = ELCP_HEX;
+                       wsi->chunk_remaining = 0;
+                       break;
+               }
+               (*buf)++;
+               (*len)--;
+       }
+
+       if (wsi->chunked && !wsi->chunk_remaining)
+               return 0;
+
+       if (wsi->u.http.content_remain &&
+           wsi->u.http.content_remain < *len)
+               n = wsi->u.http.content_remain;
+       else
+               n = *len;
+
+       if (wsi->chunked && wsi->chunk_remaining &&
+           wsi->chunk_remaining < n)
+               n = wsi->chunk_remaining;
+
+       /* hubbub */
+       if (wsi->perform_rewrite)
+               lws_rewrite_parse(wsi->rw, (unsigned char *)*buf, n);
+       else
+
+               if (user_callback_handle_rxflow(wsi->protocol->callback,
+                               wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ,
+                               wsi->user_space, *buf, n))
+                       return -1;
+
+       if (wsi->chunked && wsi->chunk_remaining) {
+               (*buf) += n;
+               wsi->chunk_remaining -= n;
+               *len -= n;
+       }
+
+       if (wsi->chunked && !wsi->chunk_remaining)
+               wsi->chunk_parser = ELCP_POST_CR;
+
+       if (wsi->chunked && *len) {
+               goto spin_chunks;
+       }
+
+       if (wsi->chunked)
+               return 0;
+
+       wsi->u.http.content_remain -= n;
+       if (wsi->u.http.content_remain || !wsi->u.http.content_length)
+               return 0;
+
+completed:
+       if (user_callback_handle_rxflow(wsi->protocol->callback,
+                       wsi, LWS_CALLBACK_COMPLETED_CLIENT_HTTP,
+                       wsi->user_space, NULL, 0))
+               return -1;
+
+       if (lws_http_transaction_completed(wsi))
+               return -1;
+
+       return 0;
+}
+
 /**
  * lws_service_fd() - Service polled socket with something waiting
  * @context:   Websocket context
@@ -716,163 +844,70 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t
 
                if (!(pollfd->revents & pollfd->events & LWS_POLLIN))
                        break;
-read:
 
+read:
                /* all the union members start with hdr, so even in ws mode
                 * we can deal with the ah via u.hdr
                 */
                if (wsi->u.hdr.ah) {
-                       lwsl_err("%s: %p: using inherited ah rx\n", __func__, wsi);
+                       lwsl_info("%s: %p: inherited ah rx\n", __func__, wsi);
                        eff_buf.token_len = wsi->u.hdr.ah->rxlen -
                                            wsi->u.hdr.ah->rxpos;
                        eff_buf.token = (char *)wsi->u.hdr.ah->rx +
                                        wsi->u.hdr.ah->rxpos;
                } else {
-
-                       eff_buf.token_len = lws_ssl_capable_read(wsi, pt->serv_buf,
-                                            pending ? pending : LWS_MAX_SOCKET_IO_BUF);
-                       switch (eff_buf.token_len) {
-                       case 0:
-                               lwsl_info("service_fd: closing due to 0 length read\n");
-                               goto close_and_handled;
-                       case LWS_SSL_CAPABLE_MORE_SERVICE:
-                               lwsl_info("SSL Capable more service\n");
-                               n = 0;
-                               goto handled;
-                       case LWS_SSL_CAPABLE_ERROR:
-                               lwsl_info("Closing when error\n");
-                               goto close_and_handled;
-                       }
-
-                       eff_buf.token = (char *)pt->serv_buf;
-               }
-
-               /*
-                * give any active extensions a chance to munge the buffer
-                * before parse.  We pass in a pointer to an lws_tokens struct
-                * prepared with the default buffer and content length that's in
-                * there.  Rather than rewrite the default buffer, extensions
-                * that expect to grow the buffer can adapt .token to
-                * point to their own per-connection buffer in the extension
-                * user allocation.  By default with no extensions or no
-                * extension callback handling, just the normal input buffer is
-                * used then so it is efficient.
-                */
-drain:
-               if (wsi->mode == LWSCM_HTTP_CLIENT_ACCEPTED) {
-                       /*
-                        * server may insist on transfer-encoding: chunked,
-                        * so http client must deal with it
-                        */
-spin_chunks:
-                       while (wsi->chunked &&
-                              (wsi->chunk_parser != ELCP_CONTENT) &&
-                              eff_buf.token_len) {
-                               switch (wsi->chunk_parser) {
-                               case ELCP_HEX:
-                                       if (eff_buf.token[0] == '\x0d') {
-                                               wsi->chunk_parser = ELCP_CR;
-                                               break;
-                                       }
-                                       n = char_to_hex(eff_buf.token[0]);
-                                       if (n < 0)
-                                               goto close_and_handled;
-                                       wsi->chunk_remaining <<= 4;
-                                       wsi->chunk_remaining |= n;
-                                       break;
-                               case ELCP_CR:
-                                       if (eff_buf.token[0] != '\x0a')
-                                               goto close_and_handled;
-                                       wsi->chunk_parser = ELCP_CONTENT;
-                                       lwsl_info("chunk %d\n",
-                                                 wsi->chunk_remaining);
-                                       if (wsi->chunk_remaining)
-                                               break;
-                                       lwsl_info("final chunk\n");
-                                       if (user_callback_handle_rxflow(
-                                                 wsi->protocol->callback,
-                                                 wsi, LWS_CALLBACK_COMPLETED_CLIENT_HTTP,
-                                                 wsi->user_space, NULL, 0))
-                                               goto close_and_handled;
-                                       if (lws_http_transaction_completed(wsi))
-                                               goto close_and_handled;
+                       if (wsi->mode != LWSCM_HTTP_CLIENT_ACCEPTED) {
+                               eff_buf.token_len = lws_ssl_capable_read(wsi,
+                                       pt->serv_buf, pending ? pending :
+                                                       LWS_MAX_SOCKET_IO_BUF);
+                               switch (eff_buf.token_len) {
+                               case 0:
+                                       lwsl_info("%s: zero length read\n", __func__);
+                                       goto close_and_handled;
+                               case LWS_SSL_CAPABLE_MORE_SERVICE:
+                                       lwsl_info("SSL Capable more service\n");
                                        n = 0;
                                        goto handled;
-
-                               case ELCP_CONTENT:
-                                       break;
-
-                               case ELCP_POST_CR:
-                                       if (eff_buf.token[0] == '\x0d') {
-                                               wsi->chunk_parser = ELCP_POST_LF;
-                                               break;
-                                       }
-                                       goto close_and_handled;
-
-                               case ELCP_POST_LF:
-                                       if (eff_buf.token[0] == '\x0a') {
-                                               wsi->chunk_parser = ELCP_HEX;
-                                               wsi->chunk_remaining = 0;
-                                               break;
-                                       }
+                               case LWS_SSL_CAPABLE_ERROR:
+                                       lwsl_info("Closing when error\n");
                                        goto close_and_handled;
                                }
-                               eff_buf.token++;
-                               eff_buf.token_len--;
-                       }
 
-                       if (wsi->chunked && !wsi->chunk_remaining) {
-                               n = 0;
-                               goto handled;
+                               eff_buf.token = (char *)pt->serv_buf;
                        }
+               }
 
-                       if (wsi->u.http.content_remain &&
-                           wsi->u.http.content_remain < eff_buf.token_len)
-                               n = wsi->u.http.content_remain;
-                       else
-                               n = eff_buf.token_len;
+drain:
+               if (wsi->mode == LWSCM_HTTP_CLIENT_ACCEPTED) {
 
-                       if (wsi->chunked && wsi->chunk_remaining &&
-                           wsi->chunk_remaining < n)
-                               n = wsi->chunk_remaining;
+                       /*
+                        * simply mark ourselves as having readable data
+                        * and turn off our POLLIN
+                        */
+                       wsi->client_rx_avail = 1;
+                       lws_change_pollfd(wsi, LWS_POLLIN, 0);
 
+                       /* let user code know, he'll usually ask for writeable
+                        * callback and drain / reenable it there
+                        */
                        if (user_callback_handle_rxflow(wsi->protocol->callback,
                                        wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP,
-                                       wsi->user_space, (void *)eff_buf.token,
-                                       n))
-                               goto close_and_handled;
-
-                       if (wsi->chunked && wsi->chunk_remaining) {
-                               eff_buf.token += n;
-                               wsi->chunk_remaining -= n;
-                               eff_buf.token_len -= n;
-                       }
-
-                       if (wsi->chunked && !wsi->chunk_remaining)
-                               wsi->chunk_parser = ELCP_POST_CR;
-
-                       if (wsi->chunked && eff_buf.token_len) {
-                               goto spin_chunks;
-                       }
-
-                       if (wsi->chunked) {
-                               n = 0;
-                               goto handled;
-                       }
-
-                       wsi->u.http.content_remain -= n;
-                       if (wsi->u.http.content_remain)
-                               goto handled;
-
-                       if (user_callback_handle_rxflow(wsi->protocol->callback,
-                                       wsi, LWS_CALLBACK_COMPLETED_CLIENT_HTTP,
                                        wsi->user_space, NULL, 0))
                                goto close_and_handled;
 
-                       if (wsi->u.http.connection_type == HTTP_CONNECTION_CLOSE)
-                               goto close_and_handled;
-                       goto handled;
+
                }
+               /*
+                * give any active extensions a chance to munge the buffer
+                * before parse.  We pass in a pointer to an lws_tokens struct
+                * prepared with the default buffer and content length that's in
+                * there.  Rather than rewrite the default buffer, extensions
+                * that expect to grow the buffer can adapt .token to
+                * point to their own per-connection buffer in the extension
+                * user allocation.  By default with no extensions or no
+                * extension callback handling, just the normal input buffer is
+                * used then so it is efficient.
+                */
                do {
                        more = 0;
 
index a647a4b..341515f 100644 (file)
@@ -80,6 +80,9 @@
 /* whether the Openssl is recent enough, and / or built with, ecdh */
 #cmakedefine LWS_HAVE_OPENSSL_ECDH_H
 
+/* HTTP Proxy support */
+#cmakedefine LWS_WITH_HTTP_PROXY
+
 /* Maximum supported service threads */
 #define LWS_MAX_SMP ${LWS_MAX_SMP}
 
index 594c8df..1535e43 100644 (file)
@@ -126,6 +126,7 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
        unsigned char *end;
        struct timeval tv;
        unsigned char *p;
+       struct lws *wsi1;
        char buf[256];
        char b64[64];
        int n, m;
@@ -161,15 +162,11 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
                        goto try_to_reuse;
                }
 
-               /* this example server has no concept of directories */
-               if (strchr((const char *)in + 1, '/')) {
-                       lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL);
-                       goto try_to_reuse;
-               }
-
 #ifndef LWS_NO_CLIENT
-               if (!strcmp(in, "/proxytest")) {
+               if (!strncmp(in, "/proxytest", 10)) {
                        struct lws_client_connect_info i;
+                       char *rootpath = "/";
+                       const char *p = (const char *)in;
 
                        if (lws_get_child(wsi))
                                break;
@@ -177,22 +174,36 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
                        pss->client_finished = 0;
                        memset(&i,0, sizeof(i));
                        i.context = lws_get_context(wsi);
-                       i.address = "example.com";
+                       i.address = "git.libwebsockets.org";
                        i.port = 80;
                        i.ssl_connection = 0;
-                       i.path = "/";
-                       i.host = "example.com";
+                       if (p[10])
+                               i.path = in + 10;
+                       else
+                               i.path = rootpath;
+                       i.host = "git.libwebsockets.org";
                        i.origin = NULL;
                        i.method = "GET";
                        i.parent_wsi = wsi;
+                       i.uri_replace_from = "git.libwebsockets.org/";
+                       i.uri_replace_to = "/proxytest/";
                        if (!lws_client_connect_via_info(&i)) {
                                lwsl_err("proxy connect fail\n");
                                break;
                        }
+
+
+
                        break;
                }
 #endif
 
+               /* this example server has no concept of directories */
+               if (strchr((const char *)in + 1, '/')) {
+                       lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL);
+                       goto try_to_reuse;
+               }
+
 #ifdef LWS_WITH_CGI
                if (!strcmp(in, "/cgitest")) {
                        static char *cmd[] = {
@@ -396,11 +407,33 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
                if (pss->fd == LWS_INVALID_FILE)
                        goto try_to_reuse;
 #ifdef LWS_WITH_CGI
-               if (pss->reason_bf) {
+               if (pss->reason_bf & 1) {
                        if (lws_cgi_write_split_stdout_headers(wsi) < 0)
                                goto bail;
 
-                       pss->reason_bf = 0;
+                       pss->reason_bf &= ~1;
+                       break;
+               }
+#endif
+#ifndef LWS_NO_CLIENT
+               if (pss->reason_bf & 2) {
+                       char *px = buf + LWS_PRE;
+                       int lenx = sizeof(buf) - LWS_PRE;
+                       /*
+                        * our sink is writeable and our source has something
+                        * to read.  So read a lump of source material of
+                        * suitable size to send or what's available, whichever
+                        * is the smaller.
+                        */
+                       pss->reason_bf &= ~2;
+                       wsi1 = lws_get_child(wsi);
+                       if (!wsi1)
+                               break;
+                       if (lws_http_client_read(wsi1, &px, &lenx) < 0)
+                               goto bail;
+
+                       if (pss->client_finished)
+                               return -1;
                        break;
                }
 #endif
@@ -470,7 +503,7 @@ bail:
         * callback for confirming to continue with client IP appear in
         * protocol 0 callback since no websocket protocol has been agreed
         * yet.  You can just ignore this if you won't filter on client IP
-        * since the default uhandled callback return is 0 meaning let the
+        * since the default unhandled callback return is 0 meaning let the
         * connection continue.
         */
        case LWS_CALLBACK_FILTER_NETWORK_CONNECTION:
@@ -478,7 +511,9 @@ bail:
                break;
 
 #ifndef LWS_WITH_CLIENT
-       case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
+       case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: {
+               char ctype[64], ctlen = 0;
+               lwsl_err("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP\n");
                p = buffer + LWS_PRE;
                end = p + sizeof(buffer) - LWS_PRE;
                if (lws_add_http_header_status(lws_get_parent(wsi), 200, &p, end))
@@ -488,14 +523,17 @@ bail:
                                (unsigned char *)"libwebsockets",
                                13, &p, end))
                        return 1;
-               if (lws_add_http_header_by_token(lws_get_parent(wsi),
+
+               ctlen = lws_hdr_copy(wsi, ctype, sizeof(ctype), WSI_TOKEN_HTTP_CONTENT_TYPE);
+               if (ctlen > 0) {
+                       if (lws_add_http_header_by_token(lws_get_parent(wsi),
                                WSI_TOKEN_HTTP_CONTENT_TYPE,
-                               (unsigned char *)"text/html", 9, &p, end))
-                       return 1;
+                               (unsigned char *)ctype, ctlen, &p, end))
+                               return 1;
+               }
 #if 0
                if (lws_add_http_header_content_length(lws_get_parent(wsi),
-                                                      file_len, &p,
-                                                      end))
+                                                      file_len, &p, end))
                        return 1;
 #endif
                if (lws_finalize_http_header(lws_get_parent(wsi), &p, end))
@@ -510,20 +548,39 @@ bail:
                if (n < 0)
                        return -1;
 
-               break;
+               break; }
        case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
+               //lwsl_err("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n");
                return -1;
                break;
        case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
-               m = lws_write(lws_get_parent(wsi), in, len, LWS_WRITE_HTTP);
+               //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p\n", wsi);
+               assert(lws_get_parent(wsi));
+               if (!lws_get_parent(wsi))
+                       break;
+               // lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p: sock: %d, parent_wsi: %p, parent_sock:%d,  len %d\n",
+               //              wsi, lws_get_socket_fd(wsi),
+               //              lws_get_parent(wsi),
+               //              lws_get_socket_fd(lws_get_parent(wsi)), len);
+               pss1 = lws_wsi_user(lws_get_parent(wsi));
+               pss1->reason_bf |= 2;
+               lws_callback_on_writable(lws_get_parent(wsi));
+               break;
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
+               //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ len %d\n", len);
+               assert(lws_get_parent(wsi));
+               m = lws_write(lws_get_parent(wsi), (unsigned char *)in,
+                               len, LWS_WRITE_HTTP);
                if (m < 0)
-                       return 1;
+                       return -1;
                break;
        case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
+               //lwsl_err("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
+               assert(lws_get_parent(wsi));
+               if (!lws_get_parent(wsi))
+                       break;
                pss1 = lws_wsi_user(lws_get_parent(wsi));
                pss1->client_finished = 1;
-               lws_callback_on_writable(lws_get_parent(wsi));
-               return -1;
                break;
 #endif
 
@@ -542,7 +599,7 @@ bail:
                        /* TBD stdin rx flow control */
                        break;
                case LWS_STDOUT:
-                       pss->reason_bf |= 1 << pss->args.ch;
+                       pss->reason_bf |= 1;
                        /* when writing to MASTER would not block */
                        lws_callback_on_writable(wsi);
                        break;